Skip to content

<goma-multi-banners>

A swipeable hero carousel of injectable banner cards. One widget renders three card types side by side:

  • sport — a sporting event. Set an eventId (live or prelive) and the widget resolves the teams, start time and odds itself; or omit eventId and supply those fields inline. The background image is always host-fed.
  • casino — a casino-game promo: category, title, description, CTA button.
  • info — a generic promo banner: title, description, CTA button.

All data is host-fed via the banners property — nothing is pulled from a CMS. The carousel ships with prev/next arrows, pagination dots, swipe/drag, and opt-in autoplay.

@gomagaming/multi-banners

Element tag

html
<goma-multi-banners></goma-multi-banners>

Installation

bash
pnpm add @gomagaming/multi-banners vue pinia vue-router vue-i18n @vueuse/core

The framework packages are peer dependencies installed once at the app level; bundlers dedup them across every @gomagaming/* widget.

Configuration

The runtime config object carries shared widget settings and — only needed for eventId-based sport banners — the sports-data connection fields.

js
el.config = {
  locale: 'en',
  // Required only when a sport banner uses `eventId` (live/prelive lookup):
  socketUrl: 'wss://…',
  socketRealm: 'sportsbook',
  ucsOperatorId: '2838',
  bettingApiBaseUrl: 'https://…',
  // Optional restyle:
  theme: { '--goma-buttonBackgroundPrimary': '#ff6600' },
}

Casino and info banners — and sport banners supplied inline — need no connection config at all.

Data sources

Banner shapeHow data gets in
sport with eventIdWidget subscribes to the event's sport feed (live + prelive) over WAMP and fills in teams, start time and odds. Host supplies only eventId (+ backgroundImage, optional sportId). Live odds tick in place.
sport without eventIdHost supplies homeTeam, awayTeam, time labels and outcomes inline.
casino / infoAlways fully host-fed.

eventId resolution note. Events are resolved from the sport's matches-aggregator feed (the same feed <goma-events-horizontal> uses). An event must be present in that live/prelive feed for the widget to fill it in; until it arrives the card shows a placeholder. For full control, supply the fields inline. Set sportId on the banner (default 1) and raise sportEventLimit if you reference less-popular events.

Live vs prelive sport cards

A sport card adapts to the event's state, mirroring the live/prelive game cards elsewhere in the family:

  • Live (isLive true) — the right-hand block shows the live clock / match part (e.g. 45' - 1st Half) and the running score (home - away). For eventId-backed cards the score and clock tick live off the same feed the odds use; an inline live card with no score data falls back to a 0 - 0 placeholder.
  • Prelive — the right-hand block shows the start date + time (e.g. Today / 14:30), from startTime or the explicit dateLabel / timeLabel fields in inline mode.

The odds row adapts its column count to the number of outcomes (1, 2 or 3), so a two-way market (e.g. an Asian-handicap line) fills the card width rather than leaving an empty third column.

The banners array

ts
type MultiBannerItem =
  | {
      id: string
      type: 'sport'
      backgroundImage?: string
      ctaUrl?: string
      // eventId mode:
      eventId?: string | number
      sportId?: number              // default 1 — scopes the feed lookup
      // inline mode (used when eventId is absent):
      competition?: string
      sportIcon?: string            // icon URL
      countryFlag?: string          // flag URL
      homeTeam?: string
      awayTeam?: string
      isLive?: boolean
      startTime?: number            // epoch ms (or use dateLabel/timeLabel)
      dateLabel?: string            // e.g. "Today"
      timeLabel?: string            // e.g. "14:30"
      outcomes?: Array<{ id?: string; label: string; odds: number; bettingOfferId?: string; isAvailable?: boolean }>
    }
  | {
      id: string
      type: 'casino'
      backgroundImage?: string
      ctaUrl?: string
      gameId?: string
      category?: string
      categoryIcon?: string         // icon URL
      title?: string
      description?: string
      ctaLabel?: string             // CTA only renders when set
    }
  | {
      id: string
      type: 'info'
      backgroundImage?: string
      ctaUrl?: string
      title?: string
      description?: string
      ctaLabel?: string
    }

Props / attributes

PropertyAttributeTypeDefaultDescription
bannersbannersMultiBannerItem[][]The banner list (JSON when set as an attribute).
selectedOutcomesselected-outcomesstring[][]Two-way mirror of selected odds ids (sport cards).
showArrowsshow-arrowsbooleantrueShow the prev/next arrows.
showDotsshow-dotsbooleantrueShow pagination dots.
centeredSlidescentered-slidesbooleantrueCentre the active slide.
centeredSlidesBoundscentered-slides-boundsbooleantrueWith centeredSlides, pin the first slide to the left edge and the last to the right (only in-between slides centre) — the natural hero behaviour.
looploopbooleantrueRewind to the start past the last slide (auto-off for one banner).
enableSwipeenable-swipebooleantrueAllow touch/drag swiping.
autoplayautoplaybooleanfalseAuto-advance on an interval.
autoplayIntervalautoplay-intervalnumber5000Autoplay delay (ms).
spaceBetweenspace-betweennumber16Gap between slides (px).
sportEventLimitsport-event-limitnumber50Max events pulled per sport feed for eventId lookups.
localelocalestring'en'i18n locale.
themethemeobject{}--goma-* overrides.

Events

All events bubble and are composed: true (cross the Shadow DOM boundary).

EventdetailFired when
goma:banner-select{ type, id, ctaUrl?, eventId?, gameId? }Any banner is activated (CTA tap, or sport-card body tap).
goma:event-navigate{ eventId, homeTeam, awayTeam, competitionName }A sport card's body is tapped — drives <goma-game-details>.
goma:outcome-select{ outcomeId, bettingOfferId, eventId, marketId, … }An odds button on a sport card is selected (same shape as <goma-events-horizontal> — a betslip widget picks it up).
ready{}Widget mounted.
error{ message, code, component? }Render error caught by the boundary.

Cross-widget cooperation

  • Drop a <goma-betslip-floating> / <goma-betslip-sidebar> on the page and odds taps on sport banners flow into it via the shared betslip storage — same contract as <goma-events-horizontal>.
  • Drop a <goma-game-details> and a sport-card body tap drives it through goma:event-navigate.

Embedding examples

Vanilla / CDN

html
<goma-multi-banners id="b"></goma-multi-banners>
<script type="module">
  import '@gomagaming/multi-banners'

  const el = document.getElementById('b')
  el.config = { socketUrl: 'wss://…', ucsOperatorId: '2838', locale: 'en' }
  el.banners = [
    { id: 's1', type: 'sport', eventId: '123456', sportId: 1, backgroundImage: 'https://…/match.jpg' },
    { id: 'c1', type: 'casino', backgroundImage: 'https://…/slot.jpg', category: 'Slots', title: 'Gates of Olympus', description: 'Spin to win', ctaLabel: 'Play', ctaUrl: 'https://…' },
    { id: 'i1', type: 'info', backgroundImage: 'https://…/promo.jpg', title: 'Welcome Bonus', description: 'Up to €100', ctaLabel: 'Claim', ctaUrl: 'https://…' },
  ]
  el.addEventListener('goma:banner-select', (e) => console.log(e.detail))
</script>

React

jsx
import '@gomagaming/multi-banners'

export function Banners({ banners, config }) {
  return (
    <goma-multi-banners
      ref={(el) => {
        if (!el) return
        el.config = config
        el.banners = banners
      }}
    />
  )
}

Vue

vue
<script setup>
import '@gomagaming/multi-banners'
import { ref, watchEffect } from 'vue'
const el = ref(null)
const props = defineProps(['banners', 'config'])
watchEffect(() => {
  if (!el.value) return
  el.value.config = props.config
  el.value.banners = props.banners
})
</script>

<template>
  <goma-multi-banners ref="el" @goma:banner-select="$emit('select', $event.detail)" />
</template>

Theming

All colours are --goma-* tokens (see the theming reference):

TokenUsed for
--goma-backgroundPrimarySection background behind the cards
--goma-backgroundOddsOdds-button background
--goma-buttonBackgroundPrimary / --goma-buttonTextPrimaryCTA button
--goma-highlightPrimaryFocus rings
--goma-textPrimaryText + active pagination dot
--goma-textSecondarySecondary text
--goma-separatorLineInactive pagination dots

The card background image is host-fed; a fixed dark gradient over it keeps text legible regardless of the image.

Source layout

LayerFile
Element classpackages/multi-banners/src/MultiBannersElement.js
App wrapperpackages/multi-banners/src/MultiBannersApp.vue
Carouselpackages/multi-banners/src/components/MultiBanners/BannerCarousel.vue
Card shell + cardspackages/multi-banners/src/components/MultiBanners/{BannerCardShell,SportBannerCard,CasinoBannerCard,InfoBannerCard}.vue
Composablespackages/multi-banners/src/composables/{useBannerEvents,useSportBannerCard}.js
Multi-event subscriptionpackages/sports-domain/src/api/everymatrix/actions.js (createBannerEventsActions)
Per-widget CSSpackages/multi-banners/src/styles/carousel.css
Testspackages/multi-banners/tests/{MultiBanners,playground-manifest}.test.js