<div class="bux-card bux-card--event-horizontal" role="group" aria-roledescription="Card" aria-label="Event Title">
    <div class="bux-card__image">
        <div class="bux-card--event__date--wrapper">
            <div class="bux-card--event__date">
                <div class="bux-card--event__date--month">Sept</div>
                <div class="bux-card--event__date--day">23</div>
            </div>
        </div>

        <div class="bux-card__image-wrapper">

            <img class="bux-image" src="https://s3.amazonaws.com/bux.osu.edu/img/example-images/oxley_9599.jpg" alt="Three young people lay hands on the face of Thompson Library bust" />

        </div>

    </div>

    <div class="bux-card__content">

        <span class="bux-card__taxonomy">Optional taxonomy</span>

        <h3 class="bux-card__heading">

            <a class="bux-card__link bux-card__heading--link" href="#" rel="noopener">
                <span>Event Title</span>
                <span class="bux-card__heading-icon" aria-hidden="true"></span>
            </a>
        </h3>

        <div class="bux-mar-top-sp-16">

            <dl class="bux-icon-dl bux-icon-dl--event-horizontal">
                <div class="bux-icon-dl__item">
                    <dt>
                        <span class="icon icon-clock" role="img" aria-hidden="true"></span>
                        Time
                    </dt>
                    <dd>
                        1:30 p.m. - 3:30 p.m.
                    </dd>
                </div>
                <div class="bux-icon-dl__item">
                    <dt>
                        <span class="icon icon-location-pin" role="img" aria-hidden="true"></span>
                        Location
                    </dt>
                    <dd>
                        BLIC / Library
                    </dd>
                </div>
            </dl>
        </div>

    </div>
</div>
{#

Card

Available variables:

- modifier:             Sets the card variant. Allowed values: null, gray,
                        no-border, linked-headline, storytelling, featured,
                        horizontal, horizontal-storytelling
- card_heading_level:   Integer. Sets the heading level of the card heading.
- card_image:           Boolean. Set to true to display image.
- image_url:            URL for the card image.
- image_alt_text:       Alt text for the card image.
- card_heading:         String for the card heading.
- card_name:            String for the type of card for aria-label. Default: "Card".
- card_body:            Content of the card body. Optional.
- card_url:             URL for the card.
- card_cta:             String for the card CTA.
- taxonomy:             String for the storytelling card taxonomy.
- read_time:            String for the storytelling card read time.

#}

{# --- Defaults & Checks --- #}
{%- set heading_level = card_heading_level|default(2) -%}
{%- set aria_role     = card_name|default("Card") -%}

{%- set is_event        = modifier in ['event', 'event-horizontal'] -%}
{%- set is_storytelling = modifier in ['storytelling', 'horizontal-storytelling'] -%}
{%- set is_horizontal   = modifier in ['horizontal', 'horizontal-storytelling', 'event-horizontal'] -%}
{%- set is_featured     = modifier == 'featured' -%}
{%- set is_linked       = modifier == 'linked-headline' or is_event or is_storytelling -%}

{# --- Date Markup --- #}
{%- set date_markup -%}
    {%- if event -%}
        <div class="bux-card--event__date--wrapper">
            <div class="bux-card--event__date">
                <div class="bux-card--event__date--month">{{ event.date.month }}</div>
                <div class="bux-card--event__date--day">{{ event.date.day }}</div>
            </div>
            {%- if event.date_end -%}
                <div class="bux-card--event__date-divider">to</div>
                <div class="bux-card--event__date">
                    <div class="bux-card--event__date--month">{{ event.date_end.month }}</div>
                    <div class="bux-card--event__date--day">{{ event.date_end.day }}</div>
                </div>
            {%- endif -%}
        </div>
    {%- endif -%}
{%- endset -%}

{# --- Card Header --- #}
{%- set card_header -%}
    <h{{ heading_level }} class="bux-card__heading">
        {%- if is_linked -%}
            {%- set heading_link_class = is_storytelling ? 'bux-card__heading--storytelling-link' : 'bux-card__heading--link' -%}
            {% embed '@link' with {
                base_class: 'bux-card__link ' ~ heading_link_class,
                link_url: card_url,
                card_heading: card_heading
            } only %}
                {% block content %}
                    <span>{{ card_heading }}</span>
                    <span class="bux-card__heading-icon" aria-hidden="true"></span>
                {% endblock %}
            {% endembed %}
        {%- else -%}
            <span>{{ card_heading }}</span>
            <span class="bux-card__heading-icon" aria-hidden="true"></span>
        {%- endif -%}
    </h{{ heading_level }}>
{%- endset -%}

{# --- Card Footer --- #}
{%- set card_footer -%}
    {%- if is_linked and is_storytelling -%}
        <span class="bux-card__read-time">{{ read_time }}</span>
    {%- elseif not is_linked and card_cta -%}
        {%- if is_featured -%}
            {% include '@link' with {
                base_class: 'bux-button bux-button--alt',
                link_url: card_url,
                link_text: card_cta,
                show_underline: false
            } only %}
        {%- else -%}
            <div class="bux-card__cta">
                {% include '@link' with {
                    base_class: 'bux-card__link',
                    link_url: card_url,
                    link_text: card_cta,
                    show_underline: false
                } only %}
            </div>
        {%- endif -%}
    {%- endif -%}
{%- endset -%}

<div
    class="bux-card{% if modifier %} bux-card--{{ modifier }}{% endif %}"
    role="group"
    aria-roledescription="{{ aria_role }}"
    {% if card_heading %}aria-label="{{ card_heading }}"{% endif %}
>
    {% if card_image %}
        {% if event %}
            <div class="bux-card__image">
            {{ date_markup }}
        {% endif %}
        
        {% if is_horizontal %}
        <div class="bux-card__image-wrapper">
        {% endif %}
        {% include '@image' %}
        {% if is_horizontal %}
        </div>
        {% endif %}

        {% if event %}
            </div>
        {% endif %}

    {% elseif event %}
        {{ date_markup }}
    {% endif %}

    <div class="bux-card__content">

        {% if taxonomy %}
            <span class="bux-card__taxonomy">{{ taxonomy }}</span>
        {% endif %}

        {{ card_header }}

        {% if card_body %}
            <div class="bux-card__body">
                {{ card_body }}
            </div>
        {% endif %}

        {% if is_event %}
            <div class="bux-mar-top-sp-16">
                {% include '@list-description-icons' with list_items %}
            </div>
        {% endif %}

        {{ card_footer }}

    </div>
</div>
site_name_prefix: Office of
site_name: Learning Relations Excellence
site_slogan: Additional text or site slogan
address_1: 100 Building Name
address_2: 1 Oval Mall
city: Columbus
state: OH
zip: '43210'
contact_email: email@osu.edu
contact_phone: 614-292-OHIO
contact_tty: 614-688-8605
image_url: https://s3.amazonaws.com/bux.osu.edu/img/example-images/oxley_9599.jpg
image_alt_text: Three young people lay hands on the face of Thompson Library bust
card_heading_level: 3
card_heading: Event Title
card_body: ''
card_url: '#'
card_cta: Call to Action
card_image: true
modifier: event-horizontal
taxonomy: Optional taxonomy
event:
  date:
    year: 2025
    month: Sept
    day: 23
list_items:
  - term: Time
    desc: 1:30 p.m. - 3:30 p.m.
    icon: clock
  - term: Location
    desc: BLIC / Library
    icon: location-pin
  • Content:
    .bux-grid.bux-card-collection {
      align-items: stretch;
    
      .bux-card {
        height: 100%;
      }
    }
    
    .bux-card {
      border-top: 4px solid $scarlet;
      color: $gray-dark-80;
      position: relative;
      display: flex;
      flex-direction: column;
      overflow: clip;
      cursor: pointer;
    }
    
    .bux-card {
      .bux-image {
        align-self: flex-start;
        object-fit: cover;
        aspect-ratio: 16/9;
        transition: transform 0.4s ease-in-out;
      }
      &:hover .bux-image {
        transform: scale(1.05);
      }
    }
    
    .bux-card__content {
      padding: $sp-24;
      background-color: $white;
      border: 2px solid $gray-light-90;
      border-top: none;
      position: relative;
      z-index: 2;
      pointer-events: none;
      flex: 1;
    }
    
    .bux-card--gray,
    .bux-card--gray .bux-card__content {
      background-color: $gray-light-90;
    }
    
    .bux-card--no-border,
    .bux-card--storytelling,
    .bux-card--horizontal,
    .bux-card--horizontal-storytelling {
      .bux-card__content {
        border: 0;
        padding: 0;
        padding-top: $sp-24;
        padding-right: $sp-24;
      }
    }
    
    .bux-card--storytelling,
    .bux-card--horizontal-storytelling {
      border-top: none;
      .bux-card__heading {
        font-family: $serif;
        font-size: $ts-md;
        font-weight: 700;
      }
    }
    .bux-card--horizontal-storytelling {
      border-bottom: 2px solid $gray-light-60;
      padding-bottom: $sp-32;
    
      .bux-card__taxonomy {
        margin-bottom: 3px;
      }
      .bux-card__heading {
        margin-top: 5px;
      }
    }
    .bux-card--event-horizontal {
      border-top: none;
    
      .bux-card__content {
        border-left: 0;
        border-right: 0;
        padding: $sp-24 0;
      }
    
      &:last-child {
        border-bottom: none;
      }
    }
    
    .bux-card__read-time {
      font-size: $ts-base;
      color: $gray-dark-40;
      margin-top: $sp-8;
    
      &::before {
        content: "\f302";
        font-family: $icon;
        font-size: $ts-sm;
        margin-right: 6px;
        margin-top: 1.5px;
        float: left;
      }
    }
    
    *.bux-card__heading {
      font-size: $ts-22;
      line-height: rem-calc(28);
      font-weight: 700;
      margin-bottom: $sp-8;
    }
    
    .bux-card__body {
      font-size: $ts-18;
      line-height: rem-calc(28);
      font-weight: 400;
      margin-top: $sp-8;
      margin-bottom: $sp-16;
    }
    
    .bux-card__cta {
      margin-top: $sp-8;
      position: relative;
      z-index: 2;
    
      a {
        color: $gray-dark-80;
        pointer-events: all;
        text-decoration: none;
        display: table;
    
        &::after {
          content: "\f005";
          font-size: $ts-sm;
          font-family: $icon;
          font-weight: 600;
          color: $scarlet;
          padding-left: $sp-8;
          transition-duration: 0.05s;
          display: table-cell;
          vertical-align: middle;
        }
    
        &:hover {
          color: $scarlet;
          &::after {
            padding-left: $sp-16;
            transition-duration: 0.05s;
          }
        }
    
        &:focus {
          outline: 2px solid $focus;
          outline-offset: $sp-4;
        }
      }
    }
    
    .bux-card--linked-headline,
    .bux-card--storytelling,
    .bux-card--horizontal-storytelling {
      .bux-card__body {
        margin-bottom: $sp-8;
      }
    }
    
    .bux-card--linked-headline {
      .bux-card__heading {
        display: inline-block;
        margin-bottom: 0;
        width: 100%;
      }
    }
    
    .bux-card__heading--link {
      text-decoration: none;
      color: $gray-dark-80;
      pointer-events: all;
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding-right: $sp-8;
      transition-duration: 0.05s;
    
      .bux-card__heading-icon {
        display: flex;
        align-items: center;
    
        &:before {
          content: "\f005";
          font-size: $ts-md;
          font-family: $icon;
          color: $scarlet;
        }
      }
    
      &:focus {
        outline: 2px solid $focus;
      }
    }
    
    .bux-card__heading--storytelling-link {
      text-decoration: none;
      color: $gray-dark-80;
      pointer-events: all;
    
      &:focus {
        outline: 2px solid $focus;
      }
    }
    
    .bux-card:hover {
      .bux-card__heading--link {
        color: $scarlet;
        text-decoration: none;
        padding-right: 0;
      }
    }
    
    .bux-card--horizontal-storytelling:hover .bux-card__heading span {
      text-decoration: none;
      box-shadow: none;
      text-shadow: none;
      color: $gray-dark-80;
    }
    
    .bux-card--storytelling:hover .bux-card__heading span,
    .bux-card--horizontal-storytelling:hover .bux-card__heading span {
      color: $gray-dark-80;
      text-decoration: none;
    }
    
    .bux-card--storytelling .bux-card__heading span:hover,
    .bux-card--horizontal-storytelling .bux-card__heading span:hover {
      color: $gray-dark-80;
    }
    .bux-card__taxonomy {
      color: $scarlet;
      text-transform: uppercase;
      font-size: $ts-sm;
      line-height: rem-calc(18);
      margin-bottom: $sp-8;
      display: inline-block;
    }
    
    .bux-card--featured {
      display: block;
      background-color: $white;
      max-width: 100%;
      margin-right: 0;
    
      .bux-card__content {
        padding: $sp-32;
      }
    
      .bux-button {
        text-decoration: none;
        display: inline-block;
      }
    
      &:hover .bux-button {
        background-color: $gray-dark-60;
        color: $white;
        border-color: $gray-dark-60;
      }
    }
    
    .bux-card__image {
      position: relative;
    }
    
    .bux-card--event,
    .bux-card--event-horizontal {
      background-color: $white;
      &__date {
        background-color: $gray-light-90;
        max-height: fit-content;
        max-width: fit-content;
        text-align: center;
        color: $scarlet;
        font-weight: 700;
        font-size: $ts-base;
        text-transform: uppercase;
        padding: $sp-8 $sp-32;
        line-height: 1.3;
    
        &--month {
          font-size: $ts-base;
        }
        &--day {
          font-size: $ts-28;
        }
    
        &-divider {
          background-color: $white;
          padding: $sp-8;
        }
      }
      .bux-card--event__date--wrapper {
        display: flex;
        align-items: center;
    
        &:has(.bux-card--event__date-divider) .bux-card--event__date {
          padding: $sp-8 $sp-24;
        }
      }
      .bux-card__image {
        .bux-card--event__date--wrapper {
          position: absolute;
          bottom: 0;
          z-index: 10;
        }
      }
    
      .bux-card__taxonomy {
        color: $gray-dark-40;
      }
      .bux-icon-dl__item {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        margin-bottom: $sp-16;
    
        &:last-child {
          margin-bottom: 0;
        }
    
        dd {
          margin: 0;
          margin-left: $sp-8;
        }
      }
    }
    
    @include breakpoint(md) {
      .bux-card--horizontal,
      .bux-card--horizontal-storytelling {
        border-top: none;
        flex-direction: row;
        max-width: 100%;
    
        .bux-card__heading {
          font-size: $ts-md;
        }
    
        .bux-image {
          max-width: 33%;
          margin-right: $sp-32;
          object-fit: cover;
        }
    
        .bux-card__content {
          padding-top: 0;
        }
      }
    
      .bux-card--event-horizontal {
        border-top: none;
        flex-direction: row;
        max-width: 100%;
        align-items: flex-start;
        border-bottom: 2px solid $gray-light-80;
        .bux-card__image {
          display: contents;
          .bux-card--event__date {
            position: relative;
            max-width: 100%;
          }
          .bux-card--event__date--wrapper {
            position: relative;
            flex-direction: column;
            align-items: stretch;
            padding-bottom: $sp-32;
          }
          .bux-card--event__date-divider {
            text-align: center;
          }
          .bux-image {
            order: 3;
            flex: 1;
            margin-bottom: $sp-24;
            max-width: 25%;
          }
        }
        .bux-card__content {
          border: none;
          align-self: stretch;
          padding: 0 $sp-24 $sp-24;
        }
      }
    
      .bux-card--horizontal-storytelling {
        .bux-card__content {
          padding-left: 0;
          border: 0;
        }
    
        .bux-card__body {
          margin-bottom: $sp-8;
        }
      }
      .bux-card--horizontal,
      .bux-card--horizontal-storytelling {
        .bux-card__image-wrapper {
          align-self: flex-start;
          aspect-ratio: 16/9;
          margin-right: $sp-32;
          max-width: 33%;
          overflow: hidden;
    
          .bux-image {
            max-width: 100%;
            margin-right: 0;
          }
        }
      }
    
      .bux-card--event-horizontal {
        .bux-card__image-wrapper {
          @include breakpoint(md) {
            order: 3;
            flex: 1;
            margin-bottom: 24px;
            max-width: 25%;
    
            align-self: flex-start;
            aspect-ratio: 16/9;
            overflow: hidden;
    
            .bux-image {
              max-width: 100%;
              margin-right: 0;
            }
          }
        }
      }
    }
    
  • URL: /components/raw/card/_card.scss
  • Filesystem Path: src/components/card/_card.scss
  • Size: 8 KB
  • Content:
    {
      /**
       * This script is intended to make the entire card component clickable while still letting users select text.
       * It works by adding a `pointerdown` listener to each card wrapper.
       * - When the pointer is pressed down, we start tracking mouse movement and listen for a pointerup.
       * - If the user moves the mouse more than the threshold, we assume they are selecting text and do nothing on pointerup.
       * - If they don't exceed the threshold, we forward a click event to the internal link on pointerup.
       *
       * This lets the whole card recieve clicks, but also preserves normal text selection, right/middle clicks, image dragging, etc.
       */
    
      const CARD_SELECTOR = ".bux-card";
      const CARD_LINK_SELECTOR = "a.bux-card__link";
      const POINTER_MOVE_THRESHOLD = 8; // in pixels
    
      const addCardClickHandling = (card) => {
        const link = card.querySelector(CARD_LINK_SELECTOR);
        if (!link) return; // Bail if we can't find the link
    
        let deltaX = 0;
        let deltaY = 0;
    
        // Prevent the link click from bubbling up to the card and looping
        link.addEventListener("click", (e) => {
          e.stopPropagation();
        });
        link.addEventListener("pointerdown", (e) => {
          e.stopPropagation();
        });
    
        card.addEventListener("click", (e) => {
          const exceedsThreshold =
            deltaX > POINTER_MOVE_THRESHOLD || deltaY > POINTER_MOVE_THRESHOLD;
          if (exceedsThreshold) {
            e.preventDefault();
            return;
          } else {
            const forwardedEvent = new PointerEvent("click", e);
            link.dispatchEvent(forwardedEvent);
          }
        });
    
        /**
         * @param {PointerEvent} e
         */
        card.addEventListener("pointerdown", (_e) => {
          /* Resetting so we can track these in the pointermove handler,
             then check if we've exceeded the threshold on pointerup */
          deltaX = 0;
          deltaY = 0;
          card.addEventListener("pointermove", onCardPointerMove, {
            passive: true,
          });
          card.addEventListener("pointerup", onCardPointerUp, {
            once: true,
          });
        });
    
        /**
         * @param {PointerEvent} e
         */
        const onCardPointerMove = (e) => {
          deltaX += Math.abs(e.movementX);
          deltaY += Math.abs(e.movementY);
        };
    
        /**
         * @param {PointerEvent} e
         */
        const onCardPointerUp = (e) => {
          card.removeEventListener("pointermove", onCardPointerMove);
          const exceedsThreshold =
            deltaX > POINTER_MOVE_THRESHOLD || deltaY > POINTER_MOVE_THRESHOLD;
          if (exceedsThreshold) {
            /* When the user has moved the pointer more than the threshold, we allow normal browser behavior by not
               clicking the link. This allows text selection, right/middle clicks on the image and CTA, image dragging, etc. */
            return;
          } else {
            /* Since this event listener only gets attached from a pointerdown event on the card,
               we can safely assume that the pointerup event should forward as a click if the
               movement threshold was not exceeded. */
            const forwardedEvent = new PointerEvent("click", e);
            link.dispatchEvent(forwardedEvent);
          }
        };
      };
    
      Array.from(document.querySelectorAll(CARD_SELECTOR)).forEach(
        addCardClickHandling,
      );
    }
    
  • URL: /components/raw/card/card.js
  • Filesystem Path: src/components/card/card.js
  • Size: 3.2 KB
  • Content:
    {
      /**
       * This script is intended to make the entire card component clickable while still letting users select text.
       * It works by adding a `pointerdown` listener to each card wrapper.
       * - When the pointer is pressed down, we start tracking mouse movement and listen for a pointerup.
       * - If the user moves the mouse more than the threshold, we assume they are selecting text and do nothing on pointerup.
       * - If they don't exceed the threshold, we forward a click event to the internal link on pointerup.
       *
       * This lets the whole card receive clicks, but also preserves normal text selection, right/middle clicks, image dragging, etc.
       */
    
      const CARD_SELECTOR = ".bux-card";
      const CARD_LINK_SELECTOR = "a.bux-card__link";
      const POINTER_MOVE_THRESHOLD = 8; // in pixels
    
      const addCardClickHandling = (card: HTMLElement) => {
        const link = card.querySelector(CARD_LINK_SELECTOR);
        if (!link) return; // Bail if we can't find the link
    
        let deltaX = 0;
        let deltaY = 0;
    
        // Prevent the link click from bubbling up to the card and looping
        link.addEventListener("click", (event) => {
          event.stopPropagation();
        });
        link.addEventListener("pointerdown", (event) => {
          event.stopPropagation();
        });
    
        card.addEventListener("click", (event) => {
          const exceedsThreshold =
            deltaX > POINTER_MOVE_THRESHOLD || deltaY > POINTER_MOVE_THRESHOLD;
          if (exceedsThreshold) {
            event.preventDefault();
            return;
          } else {
            const forwardedEvent = new PointerEvent("click", event);
            link.dispatchEvent(forwardedEvent);
          }
        });
    
        /**
         * @param {PointerEvent} e
         */
        card.addEventListener("pointerdown", () => {
          /* Resetting so we can track these in the pointermove handler,
             then check if we've exceeded the threshold on pointerup */
          deltaX = 0;
          deltaY = 0;
          card.addEventListener("pointermove", onCardPointerMove, {
            passive: true,
          });
          card.addEventListener("pointerup", onCardPointerUp, {
            once: true,
          });
        });
    
        /**
         * @param {PointerEvent} event
         */
        const onCardPointerMove = (event: MouseEvent) => {
          deltaX += Math.abs(event.movementX);
          deltaY += Math.abs(event.movementY);
        };
    
        /**
         * @param {PointerEvent} event
         */
        const onCardPointerUp = (event: MouseEvent) => {
          card.removeEventListener("pointermove", onCardPointerMove);
          const exceedsThreshold =
            deltaX > POINTER_MOVE_THRESHOLD || deltaY > POINTER_MOVE_THRESHOLD;
          if (exceedsThreshold) {
            /* When the user has moved the pointer more than the threshold, we allow normal browser behavior by not
               clicking the link. This allows text selection, right/middle-clicks on the image and CTA, image dragging, etc. */
            return;
          } else {
            /* Since this event listener only gets attached from a pointerdown event on the card,
               we can safely assume that the pointerup event should forward as a click if the
               movement threshold was not exceeded. */
            const forwardedEvent = new PointerEvent("click", event);
            link.dispatchEvent(forwardedEvent);
          }
        };
      };
    
      for (const element of document.querySelectorAll(CARD_SELECTOR) as NodeListOf<HTMLElement>) { addCardClickHandling(element); }
    
    }
    
  • URL: /components/raw/card/card.ts
  • Filesystem Path: src/components/card/card.ts
  • Size: 3.4 KB

No notes defined.