some sweeping changes that at some level involves adding a showcase
12
justfile
|
@ -98,14 +98,14 @@ setup-cargo-wasm:
|
||||||
|
|
||||||
build-website-backtrace:
|
build-website-backtrace:
|
||||||
(cd website/backtrace && wasm-pack build --target web)
|
(cd website/backtrace && wasm-pack build --target web)
|
||||||
rm -rf website/agb/src/app/vendor/backtrace
|
rm -rf website/agb/src/vendor/backtrace
|
||||||
mkdir -p website/agb/src/app/vendor
|
mkdir -p website/agb/src/vendor
|
||||||
cp website/backtrace/pkg website/agb/src/app/vendor/backtrace -r
|
cp website/backtrace/pkg website/agb/src/vendor/backtrace -r
|
||||||
|
|
||||||
build-mgba-wasm:
|
build-mgba-wasm:
|
||||||
rm -rf website/agb/src/app/mgba/vendor
|
rm -rf website/agb/src/components/mgba/vendor
|
||||||
mkdir website/agb/src/app/mgba/vendor
|
mkdir website/agb/src/components/mgba/vendor
|
||||||
{{podman_command}} build --file website/mgba-wasm/BuildMgbaWasm --output=website/agb/src/app/mgba/vendor .
|
{{podman_command}} build --file website/mgba-wasm/BuildMgbaWasm --output=website/agb/src/components/mgba/vendor .
|
||||||
|
|
||||||
build-combo-rom-site:
|
build-combo-rom-site:
|
||||||
just _build-rom "examples/combo" "AGBGAMES"
|
just _build-rom "examples/combo" "AGBGAMES"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ContentBlock } from "../contentBlock";
|
import { ContentBlock } from "../../components/contentBlock";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { styled } from "styled-components";
|
import { styled } from "styled-components";
|
||||||
|
|
||||||
|
@ -69,44 +69,48 @@ export default function ColourPicker() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentBlock>
|
<>
|
||||||
<h1>agbrs colour converter</h1>
|
<ContentBlock color="#AAAFFF">
|
||||||
<PickerWrapper>
|
<h1>agbrs colour converter</h1>
|
||||||
<PickerColumn>
|
</ContentBlock>
|
||||||
<h2>Regular RGB8</h2>
|
<ContentBlock>
|
||||||
<ColourInput
|
<PickerWrapper>
|
||||||
type="color"
|
<PickerColumn>
|
||||||
value={hexColour}
|
<h2>Regular RGB8</h2>
|
||||||
onChange={(evt) => setHexColour(evt.target.value)}
|
<ColourInput
|
||||||
/>
|
type="color"
|
||||||
<Input
|
value={hexColour}
|
||||||
type="text"
|
onChange={(evt) => setHexColour(evt.target.value)}
|
||||||
value={hexColour}
|
/>
|
||||||
onChange={(evt) => setHexColour(evt.target.value)}
|
<Input
|
||||||
/>
|
type="text"
|
||||||
</PickerColumn>
|
value={hexColour}
|
||||||
<PickerColumn>
|
onChange={(evt) => setHexColour(evt.target.value)}
|
||||||
<h2>GBA RGB5</h2>
|
/>
|
||||||
<ColourInput
|
</PickerColumn>
|
||||||
type="color"
|
<PickerColumn>
|
||||||
value={gbaHexColour}
|
<h2>GBA RGB5</h2>
|
||||||
onChange={(evt) => setGbaHexColour(evt.target.value)}
|
<ColourInput
|
||||||
/>
|
type="color"
|
||||||
<Input
|
value={gbaHexColour}
|
||||||
type="text"
|
onChange={(evt) => setGbaHexColour(evt.target.value)}
|
||||||
value={gbaHexColour}
|
/>
|
||||||
onChange={(evt) => setGbaHexColour(evt.target.value)}
|
<Input
|
||||||
/>
|
type="text"
|
||||||
<Input
|
value={gbaHexColour}
|
||||||
type="text"
|
onChange={(evt) => setGbaHexColour(evt.target.value)}
|
||||||
value={gbaU16}
|
/>
|
||||||
onChange={(evt) =>
|
<Input
|
||||||
setColour(fromRgb15(parseInt(evt.target.value, 16)))
|
type="text"
|
||||||
}
|
value={gbaU16}
|
||||||
/>
|
onChange={(evt) =>
|
||||||
</PickerColumn>
|
setColour(fromRgb15(parseInt(evt.target.value, 16)))
|
||||||
</PickerWrapper>
|
}
|
||||||
</ContentBlock>
|
/>
|
||||||
|
</PickerColumn>
|
||||||
|
</PickerWrapper>
|
||||||
|
</ContentBlock>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
import { ContentBlock } from "../contentBlock";
|
import { ContentBlock } from "../../components/contentBlock";
|
||||||
import { GameDeveloperSummary } from "./gameDeveloperSummary";
|
import { GameDeveloperSummary } from "./gameDeveloperSummary";
|
||||||
import { styled } from "styled-components";
|
import { styled } from "styled-components";
|
||||||
import { Debug } from "./debug";
|
import { Debug } from "./debug";
|
||||||
|
@ -14,30 +14,39 @@ export function BacktracePage() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentBlock>
|
<>
|
||||||
<h1>agbrs crash backtrace viewer</h1>
|
<ContentBlock color="#AAAFFF">
|
||||||
<p>
|
<h1>agbrs crash backtrace viewer</h1>
|
||||||
You likely got here from the link / QR code that was displayed when a
|
</ContentBlock>
|
||||||
game you were playing crashed. This is the default crash page for games
|
<ContentBlock>
|
||||||
made using the agb library.
|
<p>
|
||||||
</p>
|
You likely got here from the link / QR code that was displayed when a
|
||||||
<p>
|
game you were playing crashed. This is the default crash page for
|
||||||
The creator of the game is <em>very</em> likely interested in the code
|
games made using the agb library.
|
||||||
below <em>along with</em> a description of what you were doing at the
|
</p>
|
||||||
time.{" "}
|
<p>
|
||||||
<strong>Send these to the creator of the game you are playing.</strong>
|
The creator of the game is <em>very</em> likely interested in the code
|
||||||
</p>
|
below <em>along with</em> a description of what you were doing at the
|
||||||
<BacktraceCopyDisplay backtrace={backtrace} setBacktrace={setBacktrace} />
|
time.{" "}
|
||||||
<p>
|
<strong>
|
||||||
<em>
|
Send these to the creator of the game you are playing.
|
||||||
The owners of this website are not necessarily the creators of the
|
</strong>
|
||||||
game you are playing.
|
</p>
|
||||||
</em>
|
<BacktraceCopyDisplay
|
||||||
</p>
|
backtrace={backtrace}
|
||||||
<h2>Backtrace</h2>
|
setBacktrace={setBacktrace}
|
||||||
{backtrace && <Debug encodedBacktrace={backtrace} />}
|
/>
|
||||||
<GameDeveloperSummary />
|
<p>
|
||||||
</ContentBlock>
|
<em>
|
||||||
|
The owners of this website are not necessarily the creators of the
|
||||||
|
game you are playing.
|
||||||
|
</em>
|
||||||
|
</p>
|
||||||
|
<h2>Backtrace</h2>
|
||||||
|
{backtrace && <Debug encodedBacktrace={backtrace} />}
|
||||||
|
<GameDeveloperSummary />
|
||||||
|
</ContentBlock>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { styled } from "styled-components";
|
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";
|
import { ReactNode, useMemo, useState } from "react";
|
||||||
|
|
||||||
const BacktraceListWrapper = styled.div`
|
const BacktraceListWrapper = styled.div`
|
||||||
|
|
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 181 B |
|
@ -1,30 +1,18 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
import { CenteredBlock, ContentBlock } from "./contentBlock";
|
import { CenteredBlock, ContentBlock } from "../components/contentBlock";
|
||||||
import MgbaWrapper from "./mgba/mgbaWrapper";
|
import MgbaWrapper from "../components/mgba/mgbaWrapper";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
import left from "./gba-parts/left.png";
|
import left from "./left.png";
|
||||||
import right from "./gba-parts/right.png";
|
import right from "./right.png";
|
||||||
import { MobileController } from "./mobileController";
|
import { MobileController } from "../components/mobileController/mobileController";
|
||||||
import { useMemo, useRef, useState } from "react";
|
import { useMemo, useRef, useState } from "react";
|
||||||
import { GbaKey } from "./mgba/bindings";
|
import { GbaKey } from "../components/mgba/bindings";
|
||||||
import { useClientValue } from "./useClientValue.hook";
|
import { useClientValue } from "../hooks/useClientValue.hook";
|
||||||
import { MgbaHandle } from "./mgba/mgba";
|
import { MgbaHandle } from "../components/mgba/mgba";
|
||||||
|
import { ExternalLink } from "@/components/externalLink";
|
||||||
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`
|
const HelpLinks = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -78,7 +66,7 @@ function shouldStartPlaying(isTouchScreen: boolean | undefined) {
|
||||||
return !isTouchScreen;
|
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() {
|
function MgbaWithControllerSides() {
|
||||||
const mgba = useRef<MgbaHandle>(null);
|
const mgba = useRef<MgbaHandle>(null);
|
||||||
|
@ -136,13 +124,13 @@ function MgbaWithControllerSides() {
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ContentBlock>
|
<ContentBlock color="#AAAFFF">
|
||||||
<h1>agb - a rust framework for making Game Boy Advance games</h1>
|
<h1>agb - a rust framework for making Game Boy Advance games</h1>
|
||||||
</ContentBlock>
|
</ContentBlock>
|
||||||
<ContentBlock uncentered>
|
<ContentBlock uncentered>
|
||||||
<MgbaWithControllerSides />
|
<MgbaWithControllerSides />
|
||||||
</ContentBlock>
|
</ContentBlock>
|
||||||
<ContentBlock color="#f5755e">
|
<ContentBlock color="#256256">
|
||||||
<HelpLinks>
|
<HelpLinks>
|
||||||
<ExternalLink href="https://github.com/agbrs/agb">
|
<ExternalLink href="https://github.com/agbrs/agb">
|
||||||
GitHub
|
GitHub
|
||||||
|
@ -151,6 +139,7 @@ export default function Home() {
|
||||||
<ExternalLink href="https://docs.rs/agb/latest/agb/">
|
<ExternalLink href="https://docs.rs/agb/latest/agb/">
|
||||||
Docs
|
Docs
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
|
<ExternalLink href="./showcase">Showcase</ExternalLink>
|
||||||
</HelpLinks>
|
</HelpLinks>
|
||||||
</ContentBlock>
|
</ContentBlock>
|
||||||
</>
|
</>
|
||||||
|
|
Before Width: | Height: | Size: 206 B After Width: | Height: | Size: 206 B |
93
website/agb/src/app/showcase/[game]/page.tsx
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import { slugify } from "@/sluggify";
|
||||||
|
import { Games, ShowcaseGame } from "../games";
|
||||||
|
import { ContentBlock } from "@/components/contentBlock";
|
||||||
|
import { ExternalLink } from "@/components/externalLink";
|
||||||
|
import Link from "next/link";
|
||||||
|
import Image, { StaticImageData } from "next/image";
|
||||||
|
import {
|
||||||
|
ScreenshotsWrapper,
|
||||||
|
ScreenshotWrapper,
|
||||||
|
BackToShowcaseWrapper,
|
||||||
|
DescriptionAndScreenshots,
|
||||||
|
Description,
|
||||||
|
} from "./styled";
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
return Games.map((game) => ({
|
||||||
|
game: slugify(game.name),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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 Screenshots({ screenshots }: { screenshots: StaticImageData[] }) {
|
||||||
|
return (
|
||||||
|
<ScreenshotsWrapper>
|
||||||
|
{screenshots.map((screenshot) => (
|
||||||
|
<ScreenshotWrapper key={screenshot.src}>
|
||||||
|
<Image src={screenshot} alt="" />
|
||||||
|
</ScreenshotWrapper>
|
||||||
|
))}
|
||||||
|
</ScreenshotsWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Display({ game }: { game: ShowcaseGame }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ContentBlock color="#AAAFFF">
|
||||||
|
<BackToShowcaseWrapper>
|
||||||
|
<Link href="../showcase">
|
||||||
|
<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">
|
||||||
|
{game.itch && (
|
||||||
|
<ExternalLink href={game.itch.href}>View on itch.io</ExternalLink>
|
||||||
|
)}
|
||||||
|
</ContentBlock>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
36
website/agb/src/app/showcase/[game]/styled.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { styled } from "styled-components";
|
||||||
|
|
||||||
|
export const ScreenshotsWrapper = styled.div`
|
||||||
|
flex: 4;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ScreenshotWrapper = styled.div`
|
||||||
|
text-align: center;
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
width: round(down, 100%, 240px);
|
||||||
|
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;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const BackToShowcaseWrapper = styled.div`
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
`;
|
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 |
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 |
27
website/agb/src/app/showcase/games.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { StaticImageData } from "next/image";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
import { HatWiz } from "./data/tapir/hatwiz/hatwiz";
|
||||||
|
import { Purple } from "./data/tapir/purple/purple";
|
||||||
|
|
||||||
|
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[] = [HatWiz, Purple];
|
42
website/agb/src/app/showcase/page.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import { Metadata } from "next";
|
||||||
|
import { ContentBlock } from "@/components/contentBlock";
|
||||||
|
import { Games, ShowcaseGame } from "./games";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { slugify } from "@/sluggify";
|
||||||
|
import { GameDisplay, GameGrid, GameImage } from "./styles";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Games made with 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 lastImage = game.screenshots[game.screenshots.length - 1];
|
||||||
|
return (
|
||||||
|
<GameDisplay>
|
||||||
|
<Link href={`./showcase/${slugify(game.name)}`}>
|
||||||
|
<GameImage>
|
||||||
|
<Image src={lastImage} alt={`Screenshot of ${game.name}`} />
|
||||||
|
</GameImage>
|
||||||
|
<h2>{game.name}</h2>
|
||||||
|
</Link>
|
||||||
|
</GameDisplay>
|
||||||
|
);
|
||||||
|
}
|
32
website/agb/src/app/showcase/styles.tsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
export const GameGrid = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GameImage = styled.div`
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
width: round(down, 100%, 240px);
|
||||||
|
height: auto;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const GameDisplay = styled.div`
|
||||||
|
width: 600px;
|
||||||
|
a {
|
||||||
|
text-align: center;
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
`;
|
16
website/agb/src/components/externalLink.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
"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;
|
||||||
|
}
|
||||||
|
`;
|
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 styled from "styled-components";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
|
@ -8,8 +8,8 @@ import DPad from "./gba-parts/dpad.png";
|
||||||
import ABButtons from "./gba-parts/ab.png";
|
import ABButtons from "./gba-parts/ab.png";
|
||||||
import Select from "./gba-parts/SELECT.png";
|
import Select from "./gba-parts/SELECT.png";
|
||||||
import Start from "./gba-parts/START.png";
|
import Start from "./gba-parts/START.png";
|
||||||
import { GbaKey } from "./mgba/bindings";
|
import { GbaKey } from "../mgba/bindings";
|
||||||
import { MgbaHandle } from "./mgba/mgba";
|
import { MgbaHandle } from "../mgba/mgba";
|
||||||
|
|
||||||
const MobileControls = styled.div`
|
const MobileControls = styled.div`
|
||||||
display: flex;
|
display: flex;
|
|
@ -4,7 +4,7 @@ import debugInit, {
|
||||||
DebugFile,
|
DebugFile,
|
||||||
InitOutput,
|
InitOutput,
|
||||||
AddressInfo,
|
AddressInfo,
|
||||||
} from "./vendor/backtrace/backtrace";
|
} from "../vendor/backtrace/backtrace";
|
||||||
|
|
||||||
let agbDebug: Promise<InitOutput> | undefined;
|
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("-");
|
||||||
|
}
|