Start showcase pages (#657)
15
justfile
|
@ -98,18 +98,19 @@ setup-cargo-wasm:
|
|||
|
||||
build-website-backtrace:
|
||||
(cd website/backtrace && wasm-pack build --target web)
|
||||
rm -rf website/agb/src/app/vendor/backtrace
|
||||
mkdir -p website/agb/src/app/vendor
|
||||
cp website/backtrace/pkg website/agb/src/app/vendor/backtrace -r
|
||||
rm -rf website/agb/src/vendor/backtrace
|
||||
mkdir -p website/agb/src/vendor
|
||||
cp website/backtrace/pkg website/agb/src/vendor/backtrace -r
|
||||
|
||||
build-mgba-wasm:
|
||||
rm -rf website/agb/src/app/mgba/vendor
|
||||
mkdir website/agb/src/app/mgba/vendor
|
||||
{{podman_command}} build --file website/mgba-wasm/BuildMgbaWasm --output=website/agb/src/app/mgba/vendor .
|
||||
rm -rf website/agb/src/components/mgba/vendor
|
||||
mkdir website/agb/src/components/mgba/vendor
|
||||
{{podman_command}} build --file website/mgba-wasm/BuildMgbaWasm --output=website/agb/src/components/mgba/vendor .
|
||||
|
||||
build-combo-rom-site:
|
||||
just _build-rom "examples/combo" "AGBGAMES"
|
||||
gzip -9 -c examples/target/examples/combo.gba > website/agb/src/app/combo.gba.gz
|
||||
mkdir -p website/agb/src/roms
|
||||
gzip -9 -c examples/target/examples/combo.gba > website/agb/src/roms/combo.gba.gz
|
||||
|
||||
|
||||
setup-app-build: build-mgba-wasm build-combo-rom-site build-website-backtrace
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { ContentBlock } from "../contentBlock";
|
||||
import { ContentBlock } from "../../components/contentBlock";
|
||||
import { useState } from "react";
|
||||
import { styled } from "styled-components";
|
||||
|
||||
|
@ -69,8 +69,11 @@ export default function ColourPicker() {
|
|||
}
|
||||
|
||||
return (
|
||||
<ContentBlock>
|
||||
<>
|
||||
<ContentBlock color="#AAAFFF">
|
||||
<h1>agbrs colour converter</h1>
|
||||
</ContentBlock>
|
||||
<ContentBlock>
|
||||
<PickerWrapper>
|
||||
<PickerColumn>
|
||||
<h2>Regular RGB8</h2>
|
||||
|
@ -107,6 +110,7 @@ export default function ColourPicker() {
|
|||
</PickerColumn>
|
||||
</PickerWrapper>
|
||||
</ContentBlock>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { ContentBlock } from "../contentBlock";
|
||||
import { ContentBlock } from "../../components/contentBlock";
|
||||
import { GameDeveloperSummary } from "./gameDeveloperSummary";
|
||||
import { styled } from "styled-components";
|
||||
import { Debug } from "./debug";
|
||||
|
@ -14,20 +14,28 @@ export function BacktracePage() {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<ContentBlock>
|
||||
<>
|
||||
<ContentBlock color="#AAAFFF">
|
||||
<h1>agbrs crash backtrace viewer</h1>
|
||||
</ContentBlock>
|
||||
<ContentBlock>
|
||||
<p>
|
||||
You likely got here from the link / QR code that was displayed when a
|
||||
game you were playing crashed. This is the default crash page for games
|
||||
made using the agb library.
|
||||
game you were playing crashed. This is the default crash page for
|
||||
games made using the agb library.
|
||||
</p>
|
||||
<p>
|
||||
The creator of the game is <em>very</em> likely interested in the code
|
||||
below <em>along with</em> a description of what you were doing at the
|
||||
time.{" "}
|
||||
<strong>Send these to the creator of the game you are playing.</strong>
|
||||
<strong>
|
||||
Send these to the creator of the game you are playing.
|
||||
</strong>
|
||||
</p>
|
||||
<BacktraceCopyDisplay backtrace={backtrace} setBacktrace={setBacktrace} />
|
||||
<BacktraceCopyDisplay
|
||||
backtrace={backtrace}
|
||||
setBacktrace={setBacktrace}
|
||||
/>
|
||||
<p>
|
||||
<em>
|
||||
The owners of this website are not necessarily the creators of the
|
||||
|
@ -38,6 +46,7 @@ export function BacktracePage() {
|
|||
{backtrace && <Debug encodedBacktrace={backtrace} />}
|
||||
<GameDeveloperSummary />
|
||||
</ContentBlock>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { styled } from "styled-components";
|
||||
import { AddressInfo, AgbDebug, useAgbDebug } from "../useAgbDebug.hook";
|
||||
import {
|
||||
AddressInfo,
|
||||
AgbDebug,
|
||||
useAgbDebug,
|
||||
} from "../../hooks/useAgbDebug.hook";
|
||||
import { ReactNode, useMemo, useState } from "react";
|
||||
|
||||
const BacktraceListWrapper = styled.div`
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { Metadata } from "next";
|
||||
import "./globalStyles.css";
|
||||
import StyledComponentsRegistry from "./registry";
|
||||
import StyledComponentsRegistry, { BodyPixelRatio } from "./registry";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "agb - a rust framework for making Game Boy Advance games",
|
||||
|
@ -13,9 +13,9 @@ export default function RootLayout({
|
|||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
|
||||
</body>
|
||||
<StyledComponentsRegistry>
|
||||
<BodyPixelRatio>{children}</BodyPixelRatio>
|
||||
</StyledComponentsRegistry>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 181 B |
|
@ -1,35 +1,18 @@
|
|||
"use client";
|
||||
|
||||
import styled from "styled-components";
|
||||
import { CenteredBlock, ContentBlock } from "./contentBlock";
|
||||
import MgbaWrapper from "./mgba/mgbaWrapper";
|
||||
import { CenteredBlock, ContentBlock } from "../components/contentBlock";
|
||||
import MgbaWrapper from "../components/mgba/mgbaWrapper";
|
||||
import Image from "next/image";
|
||||
|
||||
import left from "./gba-parts/left.png";
|
||||
import right from "./gba-parts/right.png";
|
||||
import { MobileController } from "./mobileController";
|
||||
import left from "./left.png";
|
||||
import right from "./right.png";
|
||||
import { MobileController } from "../components/mobileController/mobileController";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { GbaKey } from "./mgba/bindings";
|
||||
import { useClientValue } from "./useClientValue.hook";
|
||||
import { MgbaHandle } from "./mgba/mgba";
|
||||
|
||||
const ExternalLink = styled.a`
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
background-color: #fad288;
|
||||
border: solid #fad288 2px;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
|
||||
&:hover {
|
||||
border: solid black 2px;
|
||||
}
|
||||
`;
|
||||
|
||||
const HelpLinks = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
`;
|
||||
import { GbaKey } from "../components/mgba/bindings";
|
||||
import { useClientValue } from "../hooks/useClientValue.hook";
|
||||
import { MgbaHandle } from "../components/mgba/mgba";
|
||||
import { ExternalLink, ExternalLinkBlock } from "@/components/externalLink";
|
||||
|
||||
const GameDisplay = styled.div`
|
||||
height: min(calc(100vw / 1.5), min(90vh, 480px));
|
||||
|
@ -78,7 +61,7 @@ function shouldStartPlaying(isTouchScreen: boolean | undefined) {
|
|||
return !isTouchScreen;
|
||||
}
|
||||
|
||||
const COMBO_GAME = new URL("combo.gba.gz", import.meta.url);
|
||||
const COMBO_GAME = new URL("../roms/combo.gba.gz", import.meta.url);
|
||||
|
||||
function MgbaWithControllerSides() {
|
||||
const mgba = useRef<MgbaHandle>(null);
|
||||
|
@ -136,14 +119,14 @@ function MgbaWithControllerSides() {
|
|||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<ContentBlock>
|
||||
<ContentBlock color="#AAAFFF">
|
||||
<h1>agb - a rust framework for making Game Boy Advance games</h1>
|
||||
</ContentBlock>
|
||||
<ContentBlock uncentered>
|
||||
<MgbaWithControllerSides />
|
||||
</ContentBlock>
|
||||
<ContentBlock color="#f5755e">
|
||||
<HelpLinks>
|
||||
<ContentBlock color="#256256">
|
||||
<ExternalLinkBlock>
|
||||
<ExternalLink href="https://github.com/agbrs/agb">
|
||||
GitHub
|
||||
</ExternalLink>
|
||||
|
@ -151,7 +134,8 @@ export default function Home() {
|
|||
<ExternalLink href="https://docs.rs/agb/latest/agb/">
|
||||
Docs
|
||||
</ExternalLink>
|
||||
</HelpLinks>
|
||||
<ExternalLink href="./showcase">Showcase</ExternalLink>
|
||||
</ExternalLinkBlock>
|
||||
</ContentBlock>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useServerInsertedHTML } from "next/navigation";
|
||||
import { ServerStyleSheet, StyleSheetManager } from "styled-components";
|
||||
import styled, { ServerStyleSheet, StyleSheetManager } from "styled-components";
|
||||
|
||||
export default function StyledComponentsRegistry({
|
||||
children,
|
||||
|
@ -27,3 +27,18 @@ export default function StyledComponentsRegistry({
|
|||
</StyleSheetManager>
|
||||
);
|
||||
}
|
||||
|
||||
const BodyWithPixelRatio = styled.body<{
|
||||
$pixel: number;
|
||||
}>`
|
||||
--device-pixel: calc(1px / ${(props) => props.$pixel});
|
||||
`;
|
||||
|
||||
export function BodyPixelRatio({ children }: { children: React.ReactNode }) {
|
||||
const [pixel, setPixel] = useState(1);
|
||||
useEffect(() => {
|
||||
setPixel(window.devicePixelRatio);
|
||||
}, []);
|
||||
|
||||
return <BodyWithPixelRatio $pixel={pixel}>{children}</BodyWithPixelRatio>;
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 206 B After Width: | Height: | Size: 206 B |
81
website/agb/src/app/showcase/[game]/page.tsx
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { slugify } from "@/sluggify";
|
||||
import { Games, ShowcaseGame } from "../games";
|
||||
import { ContentBlock } from "@/components/contentBlock";
|
||||
import { ExternalLink, ExternalLinkBlock } from "@/components/externalLink";
|
||||
import Link from "next/link";
|
||||
import {
|
||||
BackToShowcaseWrapper,
|
||||
DescriptionAndScreenshots,
|
||||
Description,
|
||||
Screenshots,
|
||||
} from "./styles";
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return Games.map((game) => ({
|
||||
game: slugify(game.name),
|
||||
}));
|
||||
}
|
||||
|
||||
function getGame(slug: string) {
|
||||
const game = Games.find((game) => slugify(game.name) === slug);
|
||||
if (!game) {
|
||||
throw new Error("Not valid game name, this should never happen");
|
||||
}
|
||||
|
||||
return game;
|
||||
}
|
||||
|
||||
export function generateMetadata({ params }: { params: { game: string } }) {
|
||||
const game = getGame(params.game);
|
||||
return { title: game.name };
|
||||
}
|
||||
|
||||
export default function Page({ params }: { params: { game: string } }) {
|
||||
const game = getGame(params.game);
|
||||
return <Display game={game} />;
|
||||
}
|
||||
|
||||
function DeveloperNames({ names }: { names: string[] }) {
|
||||
if (names.length === 0) {
|
||||
throw new Error("You must specify developer names");
|
||||
}
|
||||
if (names.length === 1) {
|
||||
return names[0];
|
||||
}
|
||||
if (names.length === 2) {
|
||||
return names.join(" and ");
|
||||
}
|
||||
const first = names.slice(0, -1);
|
||||
return first.join(", ") + `, and ${names[names.length - 1]}`;
|
||||
}
|
||||
|
||||
function Display({ game }: { game: ShowcaseGame }) {
|
||||
return (
|
||||
<>
|
||||
<ContentBlock color="#AAAFFF">
|
||||
<BackToShowcaseWrapper>
|
||||
<Link href={`../showcase#${slugify(game.name)}`}>
|
||||
<strong><</strong> Back to showcase
|
||||
</Link>
|
||||
</BackToShowcaseWrapper>
|
||||
<h1>{game.name}</h1>
|
||||
<div>
|
||||
By: <DeveloperNames names={game.developers} />
|
||||
</div>
|
||||
</ContentBlock>
|
||||
<ContentBlock>
|
||||
<DescriptionAndScreenshots>
|
||||
<Description>{game.description}</Description>
|
||||
<Screenshots screenshots={game.screenshots} />
|
||||
</DescriptionAndScreenshots>
|
||||
</ContentBlock>
|
||||
<ContentBlock color="#256256">
|
||||
<ExternalLinkBlock>
|
||||
{game.itch && (
|
||||
<ExternalLink href={game.itch.href}>View on itch.io</ExternalLink>
|
||||
)}
|
||||
</ExternalLinkBlock>
|
||||
</ContentBlock>
|
||||
</>
|
||||
);
|
||||
}
|
56
website/agb/src/app/showcase/[game]/styles.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
"use client";
|
||||
|
||||
import { styled } from "styled-components";
|
||||
import Image, { StaticImageData } from "next/image";
|
||||
|
||||
export function Screenshots({
|
||||
screenshots,
|
||||
}: {
|
||||
screenshots: StaticImageData[];
|
||||
}) {
|
||||
return (
|
||||
<ScreenshotsWrapper>
|
||||
{screenshots.map((screenshot) => (
|
||||
<Screenshot src={screenshot} alt="" key={screenshot.src} />
|
||||
))}
|
||||
</ScreenshotsWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
const ScreenshotsWrapper = styled.div`
|
||||
flex: 4;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const Screenshot = styled(Image)`
|
||||
width: 100%;
|
||||
width: max(
|
||||
round(down, 100%, calc(240 * var(--device-pixel))),
|
||||
calc(240 * var(--device-pixel))
|
||||
);
|
||||
height: auto;
|
||||
image-rendering: pixelated;
|
||||
`;
|
||||
|
||||
export const Description = styled.div`
|
||||
flex: 5;
|
||||
:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const DescriptionAndScreenshots = styled.div`
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
export const BackToShowcaseWrapper = styled.div`
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
`;
|
30
website/agb/src/app/showcase/data/tapir/dungeon/dungeon.tsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { ShowcaseGame, shuffle } from "@/app/showcase/games";
|
||||
import d1 from "./the-dungeon-puzzlers-lament-0.png";
|
||||
import d2 from "./the-dungeon-puzzlers-lament-1.png";
|
||||
|
||||
const Screenshots = [d1, d2];
|
||||
|
||||
export const Dungeon: ShowcaseGame = {
|
||||
name: "The Dungeon Puzzler's Lament",
|
||||
developers: shuffle(["Corwin Kuiper", "Gwilym Inzani"]),
|
||||
screenshots: Screenshots,
|
||||
description: (
|
||||
<>
|
||||
<p>
|
||||
Get through as many levels as possible in this space themed, dice
|
||||
rolling roguelike.
|
||||
</p>
|
||||
<p>
|
||||
Build up powerful combos to defeat enemies which keep getting stronger.
|
||||
Slowly acquire more dice and upgrade them in order to handle the
|
||||
increasing strength of the enemies you face.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Hyperspace Roll was influenced by great games such as Slay the Spire,
|
||||
FTL and the board game Escape: The Curse of the Temple.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
itch: new URL("https://setsquare.itch.io/dungeon-puzzlers-lament"),
|
||||
};
|
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 7.9 KiB |
43
website/agb/src/app/showcase/data/tapir/hatwiz/hatwiz.tsx
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { ShowcaseGame, shuffle } from "../../../games";
|
||||
import h1 from "./the-hat-chooses-the-wizard-0.png";
|
||||
import h2 from "./the-hat-chooses-the-wizard-1.png";
|
||||
import h3 from "./the-hat-chooses-the-wizard-2.png";
|
||||
import h4 from "./the-hat-chooses-the-wizard-3.png";
|
||||
|
||||
const HatWizScreenshots = [h1, h2, h3, h4];
|
||||
|
||||
export const HatWiz: ShowcaseGame = {
|
||||
name: "The Hat Chooses the Wizard",
|
||||
developers: shuffle(["Corwin Kuiper", "Gwilym Inzani"]),
|
||||
screenshots: HatWizScreenshots,
|
||||
description: (
|
||||
<>
|
||||
<p>
|
||||
‘The Hat Chooses the Wizard’ is a 2D platformer. This game
|
||||
was developed as an entry for the GMTK game jam 2021, with the theme
|
||||
“joined together”. The entire game, except for the music,
|
||||
was produced in just 48 hours.
|
||||
</p>
|
||||
<p>
|
||||
In this game, you play as a wizard searching for his missing staff.
|
||||
However, the path to the staff is filled with dangerous obstacles and
|
||||
monsters. Luckily, you have a powerful magic hat that can be thrown and
|
||||
recalled, allowing you to fly towards it and reach otherwise
|
||||
inaccessible platforms.
|
||||
</p>
|
||||
<p>
|
||||
With this unique mechanic, you can explore the game's levels and
|
||||
defeat enemies. The game's simple but challenging gameplay will put
|
||||
your platforming skills to the test as you try to reach the end.
|
||||
</p>
|
||||
<p>
|
||||
The music is by Otto Halmén released under creative commons attribution
|
||||
3.0 and can be found here:{" "}
|
||||
<a href="https://opengameart.org/content/sylvan-waltz-standard-looped-version">
|
||||
opengameart.org/content/sylvan-waltz-standard-looped-version
|
||||
</a>
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
itch: new URL("https://lostimmortal.itch.io/the-hat-chooses-the-wizard"),
|
||||
};
|
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1,30 @@
|
|||
import { ShowcaseGame, shuffle } from "@/app/showcase/games";
|
||||
import h1 from "./hyperspace-roll-0.png";
|
||||
import h2 from "./hyperspace-roll-1.png";
|
||||
|
||||
const Screenshots = [h1, h2];
|
||||
|
||||
export const Hyperspace: ShowcaseGame = {
|
||||
name: "Hyperspace Roll",
|
||||
developers: shuffle(["Corwin Kuiper", "Gwilym Inzani", "Sam Williams"]),
|
||||
screenshots: Screenshots,
|
||||
description: (
|
||||
<>
|
||||
<p>
|
||||
Get through as many levels as possible in this space themed, dice
|
||||
rolling roguelike.
|
||||
</p>
|
||||
<p>
|
||||
Build up powerful combos to defeat enemies which keep getting stronger.
|
||||
Slowly acquire more dice and upgrade them in order to handle the
|
||||
increasing strength of the enemies you face.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Hyperspace Roll was influenced by great games such as Slay the Spire,
|
||||
FTL and the board game Escape: The Curse of the Temple.
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
itch: new URL("https://lostimmortal.itch.io/hyperspace-roll"),
|
||||
};
|
26
website/agb/src/app/showcase/data/tapir/purple/purple.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { ShowcaseGame, shuffle } from "@/app/showcase/games";
|
||||
import p1 from "./the-purple-night-0.png";
|
||||
import p2 from "./the-purple-night-1.png";
|
||||
|
||||
const Screenshots = [p1, p2];
|
||||
|
||||
export const Purple: ShowcaseGame = {
|
||||
name: "The Purple Night",
|
||||
developers: shuffle(["Corwin Kuiper", "Gwilym Inzani", "Sam Williams"]),
|
||||
screenshots: Screenshots,
|
||||
description: (
|
||||
<>
|
||||
<p>Save a lost soul and take them safely back to the afterlife!</p>
|
||||
<p>
|
||||
The purple night is a platformer game where your health bar is your
|
||||
sword. The more damage you take, the shorter your sword gets, making you
|
||||
more nimble and your attacks faster, but also increasing your risk.
|
||||
</p>
|
||||
<p>
|
||||
Do you choose to stay at high health but low mobility, or low health and
|
||||
higher mobility?
|
||||
</p>
|
||||
</>
|
||||
),
|
||||
itch: new URL("https://lostimmortal.itch.io/the-purple-night"),
|
||||
};
|
After Width: | Height: | Size: 2 KiB |
After Width: | Height: | Size: 3.3 KiB |
34
website/agb/src/app/showcase/games.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { StaticImageData } from "next/image";
|
||||
import { ReactNode } from "react";
|
||||
import { HatWiz } from "./data/tapir/hatwiz/hatwiz";
|
||||
import { Purple } from "./data/tapir/purple/purple";
|
||||
import { Hyperspace } from "./data/tapir/hyperspace/hyperspace";
|
||||
import { Dungeon } from "./data/tapir/dungeon/dungeon";
|
||||
|
||||
export interface ShowcaseGame {
|
||||
name: string;
|
||||
developers: string[];
|
||||
rom?: URL;
|
||||
screenshots: StaticImageData[];
|
||||
description: ReactNode;
|
||||
itch?: URL;
|
||||
otherLink?: URL;
|
||||
}
|
||||
|
||||
export function shuffle<T>(a: T[]) {
|
||||
var j, x, i;
|
||||
for (i = a.length - 1; i > 0; i--) {
|
||||
j = Math.floor(Math.random() * (i + 1));
|
||||
x = a[i];
|
||||
a[i] = a[j];
|
||||
a[j] = x;
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
export const Games: ShowcaseGame[] = shuffle([
|
||||
HatWiz,
|
||||
Purple,
|
||||
Hyperspace,
|
||||
Dungeon,
|
||||
]);
|
39
website/agb/src/app/showcase/page.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { Metadata } from "next";
|
||||
import { ContentBlock } from "@/components/contentBlock";
|
||||
import { Games, ShowcaseGame } from "./games";
|
||||
import { slugify } from "@/sluggify";
|
||||
import { GameDisplay, GameGrid, GameImage } from "./styles";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Showcase - agb",
|
||||
};
|
||||
|
||||
export default function ColourPickerPage() {
|
||||
return (
|
||||
<>
|
||||
<ContentBlock color="#AAAFFF">
|
||||
<h1>Showcase</h1>
|
||||
</ContentBlock>
|
||||
<ContentBlock uncentered>
|
||||
<GameGrid>
|
||||
{Games.map((game, idx) => (
|
||||
<Game key={idx} game={game} />
|
||||
))}
|
||||
</GameGrid>
|
||||
</ContentBlock>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Game({ game }: { game: ShowcaseGame }) {
|
||||
const showcaseImage = game.screenshots[game.screenshots.length - 1];
|
||||
return (
|
||||
<GameDisplay
|
||||
href={`./showcase/${slugify(game.name)}`}
|
||||
id={slugify(game.name)}
|
||||
>
|
||||
<GameImage src={showcaseImage} alt={`Screenshot of ${game.name}`} />
|
||||
<h2>{game.name}</h2>
|
||||
</GameDisplay>
|
||||
);
|
||||
}
|
34
website/agb/src/app/showcase/styles.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import styled from "styled-components";
|
||||
import Image from "next/image";
|
||||
|
||||
export const GameGrid = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, min(100vw, 600px));
|
||||
justify-content: center;
|
||||
gap: 48px;
|
||||
`;
|
||||
|
||||
export const GameImage = styled(Image)`
|
||||
width: 100%;
|
||||
width: max(
|
||||
round(down, 100%, calc(240 * var(--device-pixel))),
|
||||
min(calc(240 * var(--device-pixel)), 100vw)
|
||||
);
|
||||
height: auto;
|
||||
image-rendering: pixelated;
|
||||
`;
|
||||
|
||||
export const GameDisplay = styled(Link)`
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
`;
|
|
@ -14,11 +14,8 @@ const Section = styled.section<{ $color: string }>`
|
|||
const CENTERED_CSS = `
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 60%;
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
max-width: 90%;
|
||||
}
|
||||
width: 60%;
|
||||
min-width: min(95%, 1000px);
|
||||
`;
|
||||
|
||||
export const CenteredBlock = styled.div`
|
23
website/agb/src/components/externalLink.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
"use client";
|
||||
import Link from "next/link";
|
||||
import { styled } from "styled-components";
|
||||
|
||||
export const ExternalLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
background-color: #fad288;
|
||||
border: solid #fad288 2px;
|
||||
border-radius: 5px;
|
||||
padding: 5px 10px;
|
||||
|
||||
&:hover {
|
||||
border: solid black 2px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ExternalLinkBlock = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
justify-content: space-around;
|
||||
`;
|
Before Width: | Height: | Size: 163 B After Width: | Height: | Size: 163 B |
Before Width: | Height: | Size: 173 B After Width: | Height: | Size: 173 B |
Before Width: | Height: | Size: 190 B After Width: | Height: | Size: 190 B |
Before Width: | Height: | Size: 189 B After Width: | Height: | Size: 189 B |
Before Width: | Height: | Size: 241 B After Width: | Height: | Size: 241 B |
Before Width: | Height: | Size: 156 B After Width: | Height: | Size: 156 B |
|
@ -1,4 +1,4 @@
|
|||
import { FC, useMemo, useState } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import Image from "next/image";
|
||||
|
||||
|
@ -8,8 +8,8 @@ import DPad from "./gba-parts/dpad.png";
|
|||
import ABButtons from "./gba-parts/ab.png";
|
||||
import Select from "./gba-parts/SELECT.png";
|
||||
import Start from "./gba-parts/START.png";
|
||||
import { GbaKey } from "./mgba/bindings";
|
||||
import { MgbaHandle } from "./mgba/mgba";
|
||||
import { GbaKey } from "../mgba/bindings";
|
||||
import { MgbaHandle } from "../mgba/mgba";
|
||||
|
||||
const MobileControls = styled.div`
|
||||
display: flex;
|
|
@ -4,7 +4,7 @@ import debugInit, {
|
|||
DebugFile,
|
||||
InitOutput,
|
||||
AddressInfo,
|
||||
} from "./vendor/backtrace/backtrace";
|
||||
} from "../vendor/backtrace/backtrace";
|
||||
|
||||
let agbDebug: Promise<InitOutput> | undefined;
|
||||
|
3
website/agb/src/sluggify.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function slugify(x: string) {
|
||||
return x.toLowerCase().split(" ").join("-");
|
||||
}
|