This README provides instructions on how to use the custom web components that are available in the Games Hub in your web application.

Separate pages.

First, you need a VERSION and a list of SLOT_IDs from us.

In your CMS, create an overview page. Populate it with:

<script src="https://cdn.42puzzles.com/hub/VERSION/games-hub.js" type="module"></script>
<script>
  ... (see below)
</script>
<games-hub-overview></games-hub-overview>

Then, in your CMS, create a page for each puzzle. Populate it with:

<script src="https://cdn.42puzzles.com/hub/VERSION/games-hub.js" type="module"></script>
<script>
  ... (see below)
</script>
<games-hub-player slot="SLOT_ID"></games-hub-player>

To make the links from the overview to the puzzle detail pages word, mail/slack us the URL’s of the puzzle detail pages.

Below are extra options you can use to customize and personalize the experience.

Integration

Include the Script

To use these custom elements, you need to include the following script in your HTML file. This script is responsible for lazy loading the necessary custom elements by scanning the DOM for their presence:

<script src="https://cdn.42puzzles.com/hub/VERSION/games-hub.js" type="module"></script>
<script>
  window.gamesHubEvents = window.gamesHubEvents || []
  window.gamesHubEvents.push({
    name: 'initialize',
    data: {
      // Required call - gameshub does not start without a userId
      // Pass null for anonymous users, they will receive a device-specific userid
      userId: 'USERID' | null

      // Required.
      platform: 'ios' | 'android' | 'web'

      // Required. Name of the organisation
      organisation: 'nrc' | 'mediahuis'

      // Required. Brand code within an organisation
      brand: 'tel' | 'ds' | 'indo' | ...

      // Optional. In the future we want to be able to disable features based on the subscription of a user
      // for instance, digital-only users cannot access the printed puzzles
      // A user can have multiple subscriptions, for instance 'premium' and 'digital'
      userSubscription: 'SUBNAME' | ['SUBNAME1', 'SUBNAME2'] | null

      // Optional. Defaults to `default` which follows the system theme
      theme: 'darkMode' | 'lightMode' | 'default'

      // Optional. Safe Area can be used to add extra padding to the games hub when elements are on top
      // of the Games Hub content.
      safeArea: {
        // Optional. Area added to the top and bottom of the main elements.
        body: {
          top: number,
          bottom: number,
        },
        // Optional. Area added when opening overlays.
        overlays: {
          top: number,
          bottom: number,
        },
        // Optional. Area added for notifications, like toasts. If not passed, elements using
        // this safe area use the values in 'overlays' instead.
        notifications: {
          top: number,
          bottom: number,
        }
      }

      // Optional. Feature flag overrides. Takes priority over server config.
      // Omit individual keys to use server defaults.
      features: {
        streaks?: { enabled: boolean },
        endScreen?: { enabled: boolean },
        startScreen?: { enabled: boolean },
        // When true, the game title and variant are not shown inside the player.
        // The hub emits a 'gameData' event (incl. hasOnboarding) so the parent can render the title (e.g. in the page header).
        externalGameTitle?: { enabled: boolean },
        // When true, the player's menu and onboarding buttons are not shown inside the hub.
        // The parent can render its own and open the modals via the 'showGameMenu' / 'showGameOnboarding' events.
        externalGameMenu?: { enabled: boolean },
        // When true, the hub does not handle sharing itself (no clipboard copy or confirmation dialog).
        // Instead it emits the 'share' event so the parent can trigger a native share flow.
        externalShare?: { enabled: boolean },
      }

      // Optional. Defaults to `false` which doesn't output logging to console
      debug: true | false
    }
  })
</script>

How It Works

The script automatically scans the DOM for instances of the custom elements that are available in the Games Hub. When it detects these elements, it lazy loads the required components, ensuring that only the necessary resources are loaded.

Sending events

Next to the required initialize({...}) call, there are the following optional calls:

Important: Events are processed sequentially in the order they are received and do not execute asynchronously in parallel. When calling events that rely on server data, always send the initialize event first before sending any other events.

Hub support: Not every event is available on every hub. Each event below carries an Available on badge listing the hubs that support it (NRC, MH, MH-next). initialize is supported on all hubs.

Setting Theme

Available on: NRC · MH · MH-next

If you want to update the theme during runtime, you can send the setTheme event. This will update all components of the Games Hub to match that theme.

window.gamesHubEvents.push({
  name: 'setTheme',
  data: 'darkMode' | 'lightMode' | 'default',
});

Setting SafeArea

Available on: NRC · MH-next

When screen size or orientation changes, so can the safe area. To update the safe area the Games Hub uses, you can send the setSafeArea event. The data value has the same type as the safeArea value in the initialize call.

Important: When updating the values, missing values will be interpreted as 0. It will replace all values and not retain old ones.

window.gamesHubEvents.push({name: 'setSafeArea': data: {
  // Optional
  body: {
    top: number,
    bottom: number,
  },
  // Optional
  overlays: {
    top: number,
    bottom: number,
  },
  // Optional
  notifications: {
    top: number,
    bottom: number,
  }
}})

Setting Debug

Available on: NRC · MH · MH-next

If you want to update the debug mode during runtime, you can send the setDebug value.

window.gamesHubEvents.push({
  name: 'setDebug',
  data: true | false,
});

Showing the Start Screen

Available on: NRC · MH-next

You can programmatically display the start screen by using the showStartScreen event. This is useful when you want to show the screen in response to user interactions outside the Games Hub, such as clicking links or buttons.

Note: When using the games-hub-overview component, you don’t need to send this event manually as it’s handled automatically by the component.

window.gamesHubEvents.push({
  name: 'showStartScreen',
  data: {
    gameId: 'GAME_ID', // Replace with the game identifier you want to show the screen for
    date: 'YYYY-MM-DD' | undefined, // Optional: When undefined, defaults to today.
  },
});

Closing modals, overlays and prompts

Available on: NRC · MH-next

You can programatically close any overlay by using the closeOverlays event. This will close any form of overlay.

window.gamesHubEvents.push({
  name: 'closeOverlays',
  data: {
    useAnimation: true | false,
  },
});

Notifying the hub of a focus change

Available on: NRC · MH · MH-next

Send the focus event to tell the hub the host has (re)gained focus, for example when a native app returns to the foreground or a browser tab becomes visible again. The hub uses this to refresh time-sensitive state such as the overview and start screen. The event carries no data.

window.gamesHubEvents.push({
  name: 'focus',
});

Showing the game menu and onboarding

Available on: NRC

When the external game menu feature is enabled (features.externalGameMenu.enabled), the hub does not render its own menu and onboarding buttons. Instead, the parent renders its own buttons and opens the corresponding modals by sending the showGameMenu and showGameOnboarding events. Both default useAnimation to true when omitted.

Note: showGameMenu requires features.externalGameMenu.enabled, but showGameOnboarding does not — a host can request the onboarding regardless of that flag. It is a no-op when the current game has no onboarding content (check the hasOnboarding field on the gameData event).

Mind the flag dependency: hasOnboarding only reaches you via gameData, which is emitted only when features.externalGameTitle.enabled is set. If you render your own onboarding button, enable externalGameTitle as well — otherwise you never learn whether the current game has onboarding and a showGameOnboarding request may silently do nothing.

window.gamesHubEvents.push({
  name: 'showGameMenu',
  data: {
    useAnimation: true | false,
  },
});

window.gamesHubEvents.push({
  name: 'showGameOnboarding',
  data: {
    useAnimation: true | false,
  },
});

Receiving events

The GamesHub sends events to javascript or to the window parent when the GamesHub is loaded via a native app.

For javascript (setPlatform === web), you can subscribe to events like this:

window.addEventListener('gamesHubEvent', (event) => {
  const { type, data, metadata } = event.detail;
});

For native apps (setPlatform !== web), you can subscribe to postMessage events that the webview emits. Pseudo code:

WEBVIEW.addEventListener('message', (event) => {
  const { type, data, metadata } = JSON.parse(event.detail);
});

The typing of event.detail is as follows:

{
  // timestamp this event was emitted
  timestamp: number
  // see below for possible options
  type: PlayerExternalEvent
  // see below for typing of Data per PlayerExternalEvent
  data?: Data
  timer?: {
    // time that has progressed since start of the game, excluding when it was paused. In milliseconds.
    ms: number
  }
  metadata: {
    // unique identifier for this puzzle.
    puzzleId?: string
    // date this puzzle was published. Usually today but can also be a historical puzzle. yyyy-MM-dd
    puzzleDate?: string
    // type of puzzle: wordle | headline | sudoku | etc...
    gameType?: string
    // name of this puzzle: Vorto | Precies Vier | Sudoku | etc...
    gameName?: string
    // ID of this puzzle
    gameId?: string
    // ID of the user. If anonymous user, we generate one and keep it in localStorage.
    // If it's an authenticated user, the userId is prefixed with `client-`
    userId?: string
  }
}

Where PlayerExternalEvent is one of:

Message Description Data
ready Sent when the GamesHub is ready to receive the initialize(...) call None
initialized Sent when the GamesHub has finished initializing None
started Sent when the user starts playing a game { ms: number } - Current playtime in milliseconds
paused Sent when the user pauses a game { ms: number } - Current playtime in milliseconds
completed Sent when the user completes a game { unit: 'solution' \| 'rank' \| 'points' \| 'attempts' \| 'milliseconds', value: string \| number }[]
visitUrl Sent when the user clicks a link inside the hub. On native platforms the parent is expected to perform the navigation; on web the hub opens the URL itself but still emits the event. { url: string, target: '_self' \| '_blank' }
share Sent when the user triggers the share functionality { text: string } - Text to be shared
startScreenOpened Sent when the user opens the startscreen { variant: 'singleGame' \| 'multiGame'}
gameData Sent when the player loads (and when variant/date changes). Only when features.externalGameTitle.enabled is true. Use this to render the game title and variant in your own header. hasOnboarding tells you whether a showGameOnboarding request will actually open anything. { title: string, variant: string \| undefined, date: string, relativeDate: string, hasOnboarding: boolean }
showFullScreen Sent when a fullscreen modal is opened None
hideFullScreen Sent when a fullscreen modal is closed None
modalOpened Sent when any named overlay opens (fullscreen modal, regular modal, dialog, or player menu). data.name identifies which one. { name: string } - e.g. 'startScreen', 'endScreen', 'archive', 'menu', …
modalClosed Sent when a named overlay closes. data.name matches the corresponding modalOpened. { name: string }
actionClicked Sent when the user clicks a tracked player action button (restart, hint, undo, check, focus, print, fullscreen, help, back, quit, feedback, shuffle, showWords). data.name identifies which. { name: string } - e.g. 'restart', 'hint', 'undo', …
userCheated Sent when a user automatically solves a puzzle { metadata: {userId: [userId]}, timer: {ms: number}}

Note: For fullscreen overlays (startScreen, endScreen), modalOpened and showFullScreen fire at the same moment. This is intentional — the two events serve different use cases. Non-fullscreen overlays (dialogs, regular modals, the player menu) emit only modalOpened / modalClosed.

Hub-specific event reference. The set of modalOpened / modalClosed name values, and any additional events emitted, varies per hub variant. If you’re integrating with the NRC Games Hub, see Analytics events for the NRC Games Hub for the complete list of events, payloads, and the canonical ModalName values.

games-hub-overview

The games-hub-overview element provides a grid view of available games. It displays a list of games, allowing users to browse and select games to play.

Example Usage

<games-hub-overview></games-hub-overview>

games-hub-player

The games-hub-player element provides a player for a single game.

When the external game title feature is enabled (features.externalGameTitle.enabled in the initialize call or via server config), the player does not show the game title or variant inside the component. Instead, the hub emits a gameData event with title, variant, date, relativeDate, and hasOnboarding so the parent can render the title (e.g. in the page header) and decide whether to show an onboarding button. This takes precedence over back-url: a host that renders the title is expected to render its own back button next to it, so the player draws neither. Setting both attributes previously produced two stacked title rows.

When the external game menu feature is enabled (features.externalGameMenu.enabled in the initialize call or via server config), the player does not show its menu or onboarding buttons inside the component. Instead, the parent can render its own buttons and open the corresponding modals by sending the showGameMenu and showGameOnboarding events (see Showing the game menu and onboarding).

When back-url is set and the external game title feature is off, the player renders a compact single-row header: back button, inline game title (e.g. Crux · Vandaag), timer, and menu/info controls. The host supplies the URL the back button navigates to (e.g. the puzzle overview). Navigation uses the same visitUrl path as other hub links (analytics, appview URL transform, native parent handling). On viewports below the medium breakpoint the timer moves to a separate row below the header to avoid crowding. When back-url is omitted, the header layout is unchanged from the default.

The compact header is designed for viewports at the medium breakpoint and up, where it also draws a divider along its bottom edge. There is no dedicated mobile design: below that breakpoint the divider is omitted and the header keeps the default spacing.

Attributes

  • slot: (Required) A string representing the unique identifier of the game slot to be displayed.

  • date: (Optional) A string representing the date of the game slot in YYYY-MM-DD format. Defaults to the current date.

  • back-url: (Optional) URL for the compact header back button. When present, enables the compact single-row puzzle-detail header described above. Ignored when the external game title feature is enabled.

  • download-url: (Optional) URL for a printable PDF of the puzzle. When present, adds a print action to the player menu.

Example Usage

<games-hub-player slot="12345" date="2023-10-01" back-url="/puzzels"></games-hub-player>

games-hub-history

The games-hub-history element provides a list of past instances of a game. The user can select a past instance to play again.

Attributes

  • slot: (Required) A string representing the unique identifier of the game slot.

  • date: (Optional) A string representing the date of the game slot in YYYY-MM-DD format. Defaults to the current date.

Example Usage

<games-hub-history slot="12345" date="2023-10-01"></games-hub-history>