Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
663 changes: 647 additions & 16 deletions client/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"@preact/preset-vite": "^2.10.1",
"@tanstack/router-devtools": "^1.124.0",
"typescript": "~5.8.3",
"vite": "^7.0.0"
"vite": "^7.0.0",
"vite-imagetools": "^10.0.0"
},
"volta": {
"node": "22.17.0"
Expand Down
4 changes: 2 additions & 2 deletions client/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,13 @@ p {
margin-top: 3rem;
}

@media (max-width: 768px) {
@media (width < 768px) {
.app-content {
grid-template-columns: 1fr;
}
}

@media (max-width: 768px) {
@media (width < 768px) {
.mobile-hidden {
border: 0;
clip: rect(0 0 0 0);
Expand Down
3 changes: 3 additions & 0 deletions client/src/assets/bolt.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions client/src/assets/product-images.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Type declarations for product-images.js (vite-imagetools transforms).
// Icon exports are URL strings. SrcSet exports are "url Nw, url Mw" srcset strings.

export declare const IconSwipeableAds: string;
export declare const IconCarouselAds: string;
export declare const IconVideoAds: string;
export declare const IconGameController: string;
export declare const IconCheckoutProduct: string;

export declare const PreviewSwipeableAdSrcSet: string;
export declare const PreviewCarouselAdSrcSet: string;
export declare const PreviewVideoAdSrcSet: string;
export declare const PreviewCheckoutProductSrcSet: string;

export declare const PreviewSwipeableGameSrcSet: string;
export declare const PreviewCarouselGameSrcSet: string;
export declare const PreviewVideoGameSrcSet: string;
export declare const PreviewCheckoutGameSrcSet: string;
24 changes: 24 additions & 0 deletions client/src/assets/product-images.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// vite-imagetools query-param imports.
// TypeScript resolves the companion product-images.d.ts for types.
// Vite processes this file and runs the actual imagetools transforms at build time.

// Icons: WebP at 2× display size (displayed at 80–88px), single URL
export { default as IconSwipeableAds } from "./icon-swipeable-ads.png?w=160&format=webp";
export { default as IconCarouselAds } from "./icon-carousel-ads.png?w=160&format=webp";
export { default as IconVideoAds } from "./icon-video-ads.png?w=160&format=webp";
export { default as IconGameController } from "./icon-game-controller.png?w=176&format=webp";
export { default as IconCheckoutProduct } from "./icon-checkout-product.png?w=160&format=webp";

// Ad preview images: displayed at height 400px (fixed CSS), width ~300px
// Generates two sizes and returns a "url 300w, url 600w" srcset string
export { default as PreviewSwipeableAdSrcSet } from "./preview-swipeable-ad.png?w=300;600&format=webp&as=srcset";
export { default as PreviewCarouselAdSrcSet } from "./preview-carousel-ad.png?w=300;600&format=webp&as=srcset";
export { default as PreviewVideoAdSrcSet } from "./preview-video-ad.png?w=300;600&format=webp&as=srcset";
export { default as PreviewCheckoutProductSrcSet } from "./preview-checkout-product.png?w=300;600&format=webp&as=srcset";

// Game preview images: displayed at exactly 200×250px with object-fit: cover
// Generates two sizes and returns a "url 200w, url 400w" srcset string
export { default as PreviewSwipeableGameSrcSet } from "./preview-swipeable-game.png?w=200;400&format=webp&as=srcset";
export { default as PreviewCarouselGameSrcSet } from "./preview-carousel-game.jpg?w=200;400&format=webp&as=srcset";
export { default as PreviewVideoGameSrcSet } from "./preview-video-game.jpg?w=200;400&format=webp&as=srcset";
export { default as PreviewCheckoutGameSrcSet } from "./preview-checkout-game.png?w=200;400&format=webp&as=srcset";
Comment on lines +5 to +24

Copilot AI Mar 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All generated image URLs are forced to WebP (format=webp). That will render blank in browsers without WebP support. If non-WebP clients must be supported, generate multiple formats and/or use a <picture> fallback strategy so PNG/JPEG can still load.

Suggested change
// Icons: WebP at 2× display size (displayed at 80–88px), single URL
export { default as IconSwipeableAds } from "./icon-swipeable-ads.png?w=160&format=webp";
export { default as IconCarouselAds } from "./icon-carousel-ads.png?w=160&format=webp";
export { default as IconVideoAds } from "./icon-video-ads.png?w=160&format=webp";
export { default as IconGameController } from "./icon-game-controller.png?w=176&format=webp";
export { default as IconCheckoutProduct } from "./icon-checkout-product.png?w=160&format=webp";
// Ad preview images: displayed at height 400px (fixed CSS), width ~300px
// Generates two sizes and returns a "url 300w, url 600w" srcset string
export { default as PreviewSwipeableAdSrcSet } from "./preview-swipeable-ad.png?w=300;600&format=webp&as=srcset";
export { default as PreviewCarouselAdSrcSet } from "./preview-carousel-ad.png?w=300;600&format=webp&as=srcset";
export { default as PreviewVideoAdSrcSet } from "./preview-video-ad.png?w=300;600&format=webp&as=srcset";
export { default as PreviewCheckoutProductSrcSet } from "./preview-checkout-product.png?w=300;600&format=webp&as=srcset";
// Game preview images: displayed at exactly 200×250px with object-fit: cover
// Generates two sizes and returns a "url 200w, url 400w" srcset string
export { default as PreviewSwipeableGameSrcSet } from "./preview-swipeable-game.png?w=200;400&format=webp&as=srcset";
export { default as PreviewCarouselGameSrcSet } from "./preview-carousel-game.jpg?w=200;400&format=webp&as=srcset";
export { default as PreviewVideoGameSrcSet } from "./preview-video-game.jpg?w=200;400&format=webp&as=srcset";
export { default as PreviewCheckoutGameSrcSet } from "./preview-checkout-game.png?w=200;400&format=webp&as=srcset";
// Icons at 2× display size (displayed at 80–88px), single URL
export { default as IconSwipeableAds } from "./icon-swipeable-ads.png?w=160";
export { default as IconCarouselAds } from "./icon-carousel-ads.png?w=160";
export { default as IconVideoAds } from "./icon-video-ads.png?w=160";
export { default as IconGameController } from "./icon-game-controller.png?w=176";
export { default as IconCheckoutProduct } from "./icon-checkout-product.png?w=160";
// Ad preview images: displayed at height 400px (fixed CSS), width ~300px
// Generates two sizes and returns a "url 300w, url 600w" srcset string
export { default as PreviewSwipeableAdSrcSet } from "./preview-swipeable-ad.png?w=300;600&as=srcset";
export { default as PreviewCarouselAdSrcSet } from "./preview-carousel-ad.png?w=300;600&as=srcset";
export { default as PreviewVideoAdSrcSet } from "./preview-video-ad.png?w=300;600&as=srcset";
export { default as PreviewCheckoutProductSrcSet } from "./preview-checkout-product.png?w=300;600&as=srcset";
// Game preview images: displayed at exactly 200×250px with object-fit: cover
// Generates two sizes and returns a "url 200w, url 400w" srcset string
export { default as PreviewSwipeableGameSrcSet } from "./preview-swipeable-game.png?w=200;400&as=srcset";
export { default as PreviewCarouselGameSrcSet } from "./preview-carousel-game.jpg?w=200;400&as=srcset";
export { default as PreviewVideoGameSrcSet } from "./preview-video-game.jpg?w=200;400&as=srcset";
export { default as PreviewCheckoutGameSrcSet } from "./preview-checkout-game.png?w=200;400&as=srcset";

Copilot uses AI. Check for mistakes.
5 changes: 5 additions & 0 deletions client/src/assets/sdk-images.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Type declarations for sdk-images.js (vite-imagetools transforms).
// All exports are "url Nw, url Mw" srcset strings.

export declare const PreviewUnitySrcSet: string;
export declare const PreviewJSSrcSet: string;
7 changes: 7 additions & 0 deletions client/src/assets/sdk-images.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// vite-imagetools query-param imports.
// TypeScript resolves the companion sdk-images.d.ts for types.
// Vite processes this file and runs the actual imagetools transforms at build time.

// SDK preview images: displayed at width 400px (fixed CSS)
export { default as PreviewUnitySrcSet } from "./preview-unity.png?w=400;800&format=webp&as=srcset";
export { default as PreviewJSSrcSet } from "./preview-js.jpg?w=400;800&format=webp&as=srcset";

Copilot AI Mar 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These srcset assets are generated as WebP-only (format=webp), which will fail to display in browsers without WebP support. If you need broader compatibility, consider generating a fallback format and serving via <picture> or multi-format outputs.

Suggested change
export { default as PreviewJSSrcSet } from "./preview-js.jpg?w=400;800&format=webp&as=srcset";
export { default as PreviewJSSrcSet } from "./preview-js.jpg?w=400;800&format=webp;jpg&as=picture";

Copilot uses AI. Check for mistakes.
8 changes: 8 additions & 0 deletions client/src/components/page-layout/PageLayout.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,11 @@

margin-bottom: var(--bolt-space-8);
}

.hero h1 {
text-align: center;
}

.hero p {
text-align: center;
}
1 change: 1 addition & 0 deletions client/src/components/section/Section.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
@media (width < 768px) {
.section {
grid-template-columns: 1fr;
gap: var(--bolt-space-10);
}

.preview {
Expand Down
57 changes: 55 additions & 2 deletions client/src/components/top-nav/TopNav.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,74 @@
z-index: 100;
}

@media (width < 768px) {
.topNav {
padding: var(--bolt-space-3) var(--bolt-space-4);
}
}

.navContainer {
display: flex;
align-items: center;
}

.navLeft {
display: flex;
align-items: center;
gap: 4rem;
gap: 1.5rem;
}

.navBrand {
display: flex;
align-items: center;
flex-shrink: 0;
}

.navBrandLink {
display: flex;
align-items: center;
color: var(--bolt-content-primary);
}

.navLogoFull {
height: 24px;
display: block;
}

.navLogoIcon {
height: 24px;
width: 24px;
display: none;
}

@media (max-width: 640px) {
.navLogoFull {
display: none;
}

.navLogoIcon {
display: block;
}
}

.navLinks {
margin-top: 0.5rem;
display: flex;
gap: var(--bolt-space-8);
gap: var(--bolt-space-6);
flex-wrap: wrap;
}

@media (width < 480px) {
.navLinks {
gap: var(--bolt-space-4);
}
}

.navLink {
color: var(--bolt-content-secondary);
text-decoration: none;
white-space: nowrap;
font-size: clamp(0.8rem, 2.5vw, 1rem);
}

.active {
Expand Down
8 changes: 7 additions & 1 deletion client/src/components/top-nav/TopNav.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Link } from "@tanstack/react-router";

import BoltIcon from "../../assets/bolt.svg";
import BoltLightningGames from "../../assets/lightning-games.svg";

import styles from "./TopNav.module.css";
Expand All @@ -14,7 +15,12 @@ export function TopNav() {
<img
src={BoltLightningGames}
alt="Game Logo"
className={styles.navLogo}
className={styles.navLogoFull}
/>
<img
src={BoltIcon}
alt="Game Logo"
className={styles.navLogoIcon}
/>
</Link>
</div>
Expand Down
8 changes: 4 additions & 4 deletions client/src/design/heading/Heading.module.css
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
.heading1 {
font-size: var(--bolt-font-heading-1-size);
line-height: 0;
line-height: 1.2;
font-weight: var(--bolt-font-heading-1-weight);
margin-block: 0;
}
.heading1.large {
font-size: var(--bolt-font-heading-1-large-size);
line-height: 0;
}

.heading1.xlarge {
font-size: var(--bolt-font-heading-1-xlarge-size);
line-height: 0;
}

.heading2 {
font-size: var(--bolt-font-heading-2-size);
line-height: 0;
line-height: 1.2;
font-weight: var(--bolt-font-heading-2-weight);
margin-block: 0;
}
7 changes: 7 additions & 0 deletions client/src/globals.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
/// <reference types="vite/client" />

// vite-imagetools: query-param image imports return a URL string by default.
// Using wildcards here because TypeScript can't resolve arbitrary query strings.
declare module "*.png?w=160&format=webp" {
const src: string;
export default src;
}

declare global {
interface Window {
BoltSDK?: any;
Expand Down
70 changes: 44 additions & 26 deletions client/src/pages/products/Products.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
import Tabs from "../../design/tabs/Tabs";
import { Section, Sections } from "../../components/section/Section";

import IconSwipeableAds from "../../assets/icon-swipeable-ads.png";
import PreviewSwipeableAd from "../../assets/preview-swipeable-ad.png";
import PreviewSwipeableGame from "../../assets/preview-swipeable-game.png";

import IconCarouselAds from "../../assets/icon-carousel-ads.png";
import PreviewCarouselAd from "../../assets/preview-carousel-ad.png";
import PreviewCarouselGame from "../../assets/preview-carousel-game.jpg";

import IconVideoAds from "../../assets/icon-video-ads.png";
import PreviewVideoAd from "../../assets/preview-video-ad.png";
import PreviewVideoGame from "../../assets/preview-video-game.jpg";

import IconGameController from "../../assets/icon-game-controller.png";

import IconCheckoutProduct from "../../assets/icon-checkout-product.png";
import PreviewCheckoutProduct from "../../assets/preview-checkout-product.png";
import PreviewCheckoutGame from "../../assets/preview-checkout-game.png";
import {
IconSwipeableAds,
IconCarouselAds,
IconVideoAds,
IconGameController,
IconCheckoutProduct,
PreviewSwipeableAdSrcSet,
PreviewCarouselAdSrcSet,
PreviewVideoAdSrcSet,
PreviewCheckoutProductSrcSet,
PreviewSwipeableGameSrcSet,
PreviewCarouselGameSrcSet,
PreviewVideoGameSrcSet,
PreviewCheckoutGameSrcSet,
} from "../../assets/product-images";

import styles from "./Product.module.css";
import { Heading1 } from "../../design/heading/Heading";
Expand Down Expand Up @@ -76,7 +73,10 @@ function AdsProductContent() {
/>
}
preview={
<Preview adUrl={PreviewSwipeableAd} gameUrl={PreviewSwipeableGame} />
<Preview
adSrcSet={PreviewSwipeableAdSrcSet}
gameSrcSet={PreviewSwipeableGameSrcSet}
/>
}
/>
<Section
Expand All @@ -90,7 +90,10 @@ function AdsProductContent() {
/>
}
preview={
<Preview adUrl={PreviewCarouselAd} gameUrl={PreviewCarouselGame} />
<Preview
adSrcSet={PreviewCarouselAdSrcSet}
gameSrcSet={PreviewCarouselGameSrcSet}
/>
}
/>
<Section
Expand All @@ -103,7 +106,12 @@ function AdsProductContent() {
label="View Experience"
/>
}
preview={<Preview adUrl={PreviewVideoAd} gameUrl={PreviewVideoGame} />}
preview={
<Preview
adSrcSet={PreviewVideoAdSrcSet}
gameSrcSet={PreviewVideoGameSrcSet}
/>
}
/>
</Sections>
);
Expand All @@ -119,20 +127,30 @@ function CheckoutProductContent() {
action={<CheckoutAction label="View Experience" />}
preview={
<Preview
adUrl={PreviewCheckoutProduct}
gameUrl={PreviewCheckoutGame}
adSrcSet={PreviewCheckoutProductSrcSet}
gameSrcSet={PreviewCheckoutGameSrcSet}
/>
}
/>
</Sections>
);
}

function Preview({ adUrl, gameUrl }: { adUrl: string; gameUrl: string }) {
function Preview({ adSrcSet, gameSrcSet }: { adSrcSet: string; gameSrcSet: string }) {
return (
<div className={styles.preview}>
<img src={adUrl} className={styles.previewAd} alt="Preview Ad" />
<img src={gameUrl} className={styles.previewGame} alt="Preview Game" />
<img
srcSet={adSrcSet}
sizes="300px"
className={styles.previewAd}
alt="Preview Ad"
/>
<img
srcSet={gameSrcSet}
sizes="200px"
className={styles.previewGame}
alt="Preview Game"
/>
</div>
);
}
1 change: 1 addition & 0 deletions client/src/pages/sdks/DeveloperSDKs.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@

.previewImage {
width: 400px;
max-width: 100%;
}
10 changes: 5 additions & 5 deletions client/src/pages/sdks/DeveloperSDKs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ import { Section, Sections } from "../../components/section/Section";
import { Heading1 } from "../../design/heading/Heading";
import { TextBlock } from "../../design/text-block/TextBlock";

import PreviewUnity from "../../assets/preview-unity.png";

import PreviewJS from "../../assets/preview-js.jpg";
import { PreviewUnitySrcSet, PreviewJSSrcSet } from "../../assets/sdk-images";

import styles from "./DeveloperSDKs.module.css";
import { LinkButton } from "../../design/button/Button";
Expand Down Expand Up @@ -37,7 +35,8 @@ export function DevelopmentSDKs() {
}
preview={
<img
src={PreviewUnity}
srcSet={PreviewUnitySrcSet}
sizes="400px"
className={styles.previewImage}
alt="Unity SDK Preview"
/>
Expand All @@ -57,7 +56,8 @@ export function DevelopmentSDKs() {
}
preview={
<img
src={PreviewJS}
srcSet={PreviewJSSrcSet}
sizes="400px"
className={styles.previewImage}
alt="TypeScript SDK Preview"
/>
Expand Down
Loading
Loading