Bruno DelorenceBruno
Appearance
Appearance

Choose your reading theme.

Contact

Loading tip

New

A game’s loading screen: a rotating tip, the run’s progress at a glance, a turning loader, then a Continue prompt.

loading-tip/loading-tip.tsx
Live · interactive
Heart containers
32
Stamina wheels
2.5
Spirit orbs
7
Shards
69
Seed pods
420
Gems
80085

Climbing Tip

The live component, numbered part by part. Every measurement is read from it as it is drawn. Point at a row to outline its part, or at a part to find its row.

When to use

  • A full-screen wait between regions or levels, long enough (a few seconds or more) to read a line of advice.
  • Teaching one mechanic at a time while the player waits, with tips they can step through by hand.
  • Showing the run at a glance on the way in: hearts, stamina, collected items and the creatures freed so far.
  • A load that should end on the player’s cue, with a Continue prompt instead of dropping them straight into play.
  • Marking progress between two loads: counters that tick up and a newly freed creature that fills in.

When not to use

  • A wait under a second or two: the tip cannot be read. Use the loader alone, or nothing.
  • Loading inside a menu or a panel that stays on screen. Use an inline spinner or a progress bar.
  • A download or an install with a known size. Use a progress bar with a percentage.
  • The run’s stats while playing. Use the HUD pieces (heart meter, stamina wheel, coin counter).
  • A game over or a chapter end with choices to make. Use the game over screen or a game modal.

You can add this component with the shadcn CLI or by hand:

Into components/loading-tip of any React project, with no setup:

Terminal
npx shadcn@latest add https://brunodelorence.com/r/loading-tip.json

Either way it needs React 19 and a bundler that supports CSS Modules, such as Next.js or Vite. It has a dark colour scheme: it follows your page’s color-scheme, light unless the page sets one, or its colorScheme prop, with nothing to configure. Browsers without light-dark() show it light.

Usage

region-loader.tsx
import { LoadingTip, type LoadingTipItem } from './loading-tip';const TIPS: LoadingTipItem[] = [  { title: 'Climbing Tip', body: 'Rest on every ledge: stamina refills only while you stand still.' },  { title: 'Cooking Tip', body: 'A warming herb in the pot keeps the cold out for a while.' },];export function RegionLoader({ loaded, onEnter }: { loaded: boolean; onEnter: () => void }) {  return (    <LoadingTip      tips={TIPS}      hearts={12}      stamina={1.5}      freed={2}      ready={loaded}      onContinue={onEnter}    />  );}

Each state below is a live instance, locked so you can study it.

The pieces it is built from, each with its measurements read live.

The screen a game shows while it loads the next region. A cream band holds the run at a glance: two rows of heart containers, stamina wheels, four collected-item counters and the creatures freed so far, standing on the band’s edge, with the tip’s title. The black below holds the advice and a small six-dot loader. Tips rotate while it loads; when it is done, a Continue prompt with the A button breathes until it is pressed and the screen fades to black. The next load ticks its counters up and fills in a newly freed creature. Click the tip or use the arrow keys to read the next one.

  • The screen is drawn in px of a 1920 × 1080 screen: --u is 100cqw / 1920, a container query unit, and every size is a multiple of it (a 426 px cream band, 19 × 17 hearts 8 px apart in rows of 16, 30 px wheels, 34 px icons 40 px apart, a 43 px title, 31 px advice on a 38 px line), so from 600 px up it scales as one 16:9 picture.
  • Below 600 px the band’s parts stop overlapping and stack in flow (stats, creatures, then the title), --u keeps a floor of 0.36 px so the art stays legible, and the text keeps a minimum size; the screen grows as tall as it needs.
  • All the art is SVG in currentColor with cut-outs in the band’s cream: hearts, wheels (a part-filled wheel is the left arc of the ring, so half a wheel reads as a crescent), an orb, a shard, a seed pod and a gem, and five creatures built from plain shapes, each placed along one ground line in px of the screen.
  • Tips rotate every tipInterval ms while loading: the old title and advice fade and lift in 200 ms, then the new title settles in and its advice follows 70 ms later. Clicking the tip, or the arrow keys on it, steps through them by hand, and a polite live region reads the new tip.
  • The loader is six dots on a ring, alternately big and small: the ring turns once every 2.4 s while each dot swaps between full and 60% size, out of phase with its neighbours, so the big dots seem to travel round.
  • When loading is done (ready, or uncontrolled after loadTime), the loader fades and the Continue prompt slides in with an overshoot, its verb and button glowing every 1.8 s. Pressing it (a click, Enter, Space, or A anywhere in the screen) dips the button with a ring of light while a black overlay fades in over 420 ms; onContinue is called once the screen is black.
  • Each load is a new screen: it fades up from black, and its progress plays against the screen before it. A counter that went up counts to its new value with an ease-out (shown by script, with the real value for screen readers), and its icon pops; a newly freed creature fills in from its ghost, swells and settles, with a warm flash behind it.
  • The demo, while it is on screen and untouched, plays two loads in a row: the first from a slightly earlier run, the second at the props, so each round shows a tip change, the prompt, the press, the fade and the progress. Any input hands it to the reader.
  • The screen keeps its own cream and black on a light or a dark page; only its edge and its shadow on the page are light-dark() tokens.
PropTypeDefaultDescription
tipsLoadingTipItem[]five tipsThe tips it rotates through, each { title, body }.
defaultTipnumber0The index of the tip it starts on.
tipIntervalnumber6000Ms between tips while loading; 0 keeps one tip. Rotation stops once it is ready.
heartsnumber32Heart containers, drawn 16 to a row.
staminanumber2.5Stamina in wheels: whole rings, plus a crescent for the fraction.
countersLoadingCounter[]orbs 7, shards 69, seeds 420, gems 80085The collected-item counters, top to bottom, each { id, label, value, icon } with icon 'orb', 'shard', 'seed' or 'gem'. A value that went up since the last screen ticks up.
freednumber4How many of the four creatures are freed and drawn solid; the rest stand as faint ghosts. One freed since the last screen fills in with a flash.
roverbooleantrueShows the small rover at the start of the creature row.
readyboolean—Loading is done (controlled): the loader gives way to the Continue prompt. Without it, the component finishes on its own after loadTime.
loadTimenumber9000Uncontrolled, how long each load takes, in ms.
continueLabelstring'Continue'The verb beside the A button.
onContinue() => void—Called when Continue is pressed, once the screen has faded to black.
autoplaybooleantruePlays two loads in a row while on screen and untouched (uncontrolled only); any input hands it to the reader.
colorScheme'light' | 'dark' | 'system'—Sets color-scheme on the root, for the screen’s edge and shadow; the screen keeps its own colours.
classNamestring—Added to the root.

The complete source behind this demo. Open source under the MIT license, © 2026 Bruno Delorence: copy it, change it, ship it.

loading-tip.tsx
// Copyright (c) 2026 Bruno Delorence. MIT License.'use client';import {  useCallback,  useEffect,  useEffectEvent,  useRef,  useState,  type CSSProperties,  type KeyboardEvent,} from 'react';import { CREATURES, Heart, Icon, ROVER, ROW, Wheel, type CounterIcon } from './art';import { useAutoplay } from './use-autoplay';import styles from './loading-tip.module.css';/* * Loading tip: the screen a game shows while it loads the next region. A cream band holds the * run's progress at a glance (heart containers, stamina wheels, four collected-item counters and * the creatures freed so far, standing on the band's edge) and a tip's title; the black below * holds the tip's advice and a small loader turning in the corner. * * - The screen is drawn in px of a 1920 × 1080 screen (--u, one such px, is 1/1920 of its width),
loading-tip.module.css
/* Loading tip (loading-tip.tsx): a whole loading screen, centred on the plain stage. It is drawn   in --u, one px of a 1920 × 1080 screen (1/1920 of the screen's width, a container query unit),   so at 600 px and up it is one 16:9 picture: the cream band is 426 px of it, the black the rest.   Below 600 px the band's parts stack in flow instead, --u keeps a floor so the art stays legible,   and the text keeps a minimum size.   The screen keeps its own colours on a light or a dark page (it is a game's screen, cream and   black); only its edge and its shadow on the page follow the colour scheme, through light-dark()   tokens. Art is SVG in currentColor with cut-outs in --band, so it stays sharp at any density.   Motion: the screen fades up from black and back down (an overlay's opacity); the loader turns   (a transform) while its dots swap sizes; a tip leaves by a transition and arrives by keyframes;   a newly freed creature fills in and flashes; counters tick by script. */.root {  /* The screen. */  --band: #fffde4;  --ink: #000;  --night: #000;  --tip-ink: rgb(255 255 255 / 0.9);  --dot: #e2ded3;  --glyph: #e1ded1;  --glyph-ink: #24221d;  --verb: #e0ded1;
art.tsx
// Copyright (c) 2026 Bruno Delorence. MIT License./* * The loading screen's art, drawn as SVG in px of a 1920 × 1080 screen (the CSS sizes each piece * in --u, one such px). Everything is flat ink (currentColor) with a few cut-outs in the band's * cream (--band), as stamped silhouettes are: * * - Heart: a 19 × 17 heart. Wheel: a 30 px ring, 7.5 px thick; a partial wheel is the left part *   of that ring, so less than a whole wheel reads as a crescent. * - Counter icons, 34 px: a dark orb with a cream fan, a diamond shard, a seed pod, a long gem. * - The creatures, standing on one ground line: a small rover, a rising hawk, a stilt beast with *   a crest, a skink with a curled tail, a shelled tortoise. Each is a group of plain shapes. */export type CounterIcon = 'orb' | 'shard' | 'seed' | 'gem';export function Heart() {  return (    <svg viewBox="0 0 19 17" aria-hidden="true">      <path d="M9.5 17 1.4 8.6A4.7 4.7 0 0 1 9.5 2.5a4.7 4.7 0 0 1 8.1 6.1Z" fill="currentColor" />    </svg>  );}
use-autoplay.ts
// Copyright (c) 2026 Bruno Delorence. MIT License.'use client';import { useCallback, useEffect, useRef, useState } from 'react';/* * When a demo may play on its own: while part of it is on screen and the tab is visible, never * under reduced motion, and not while the reader is using it. `take()` hands it to the reader; it * takes back over `idle` ms after their last input. */export function useAutoplay<T extends HTMLElement>(idle = 6000) {  const ref = useRef<T>(null);  const [visible, setVisible] = useState(false);  const [reduced, setReduced] = useState(true);  const [taken, setTaken] = useState(false);  const timer = useRef(0);  useEffect(() => {    const element = ref.current;    if (!element) return;    let onScreen = false;    const update = () => setVisible(onScreen && !document.hidden);    const observer = new IntersectionObserver(
  • The screen is a section labelled Loading screen, aria-busy while it loads. The tip’s title is a heading; its advice is a button (Next tip) that the arrow keys step through, and a polite live region reads a tip the reader moved to, and says when loading is done.
  • The progress is a description list: heart containers, stamina wheels and each counter by name, with the real value in text even while the shown number ticks. The creatures are a list labelled with how many are freed, each named, the ghosts marked not freed yet. The art itself is aria-hidden.
  • Continue is a real button, reached by Tab and pressed by Enter or Space; A presses it while the focus is in the screen. If it had the focus when it goes, the focus moves to the tip.
  • Every control shows a focus ring. Under reduced motion there is no demo, the fades, tip transitions, fill-ins and glows are off, and the loader stops turning and only pulses slowly; the tips still change, without movement.