Give it a container and a page of its own
A Dockerfile that builds the bundle and serves it from nginx, matching the three games already on the site, and the metadata a page needs when it is somewhere rather than on a laptop: canonical, Open Graph, the two JSON-LD blocks, and a noscript that describes the game rather than showing an empty div to anybody who does not run scripts. No dev proxy, unlike its siblings. They post scores to a shared service and this one has nothing to post: a game of Diplomacy is not a number, and a leaderboard reading "reached eleven centres" would be a worse thing to know about somebody than nothing at all. The favicon is seven powers round a board in the seven colours the map uses, with the one supply centre in the middle they are all looking at -- drawn here rather than drafted in a tool, like every other mark in this repository. And a way back out, absolute rather than "/": this is served from a subdirectory in production and from the root in development, so the relative answer is right in one of those and points at the game itself in the other.
This commit is contained in:
+131
-1
@@ -3,11 +3,141 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Great Powers — the classic negotiation game, in your browser</title>
|
||||
<title>Great Powers — the classic negotiation game, in your browser</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Europe in 1901, seven powers, thirty-four supply centres and no dice. Every unit is as strong as every other, so nothing happens that somebody did not agree to help with. One of you against six who negotiate, agree, and break their word. Free, no account, nothing to install."
|
||||
/>
|
||||
<meta name="author" content="John Coffey" />
|
||||
|
||||
<!--
|
||||
Canonical, because the container serving this falls back to index.html
|
||||
for any path under /powers/ rather than returning a 404. That is right
|
||||
for the app and wrong for a crawler, which would otherwise be free to
|
||||
index /powers/anything-at-all as a separate page with identical content.
|
||||
-->
|
||||
<link rel="canonical" href="https://games.jcoffey.dev/powers/" />
|
||||
<meta name="robots" content="index,follow,max-image-preview:large,max-snippet:-1,max-video-preview:-1" />
|
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="theme-color" content="#1b2437" />
|
||||
|
||||
<meta property="og:site_name" content="Games · jcoffey.dev" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="Great Powers — the classic negotiation game, in your browser" />
|
||||
<meta property="og:url" content="https://games.jcoffey.dev/powers/" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Europe, 1901. Seven powers, thirty-four supply centres and no dice. Nothing happens on this board that somebody did not agree to help with."
|
||||
/>
|
||||
<!--
|
||||
The preview image is served by the site rather than carried here.
|
||||
Everything in this repository is generated in code -- there is no artwork
|
||||
file and there is not going to be one -- and a social card is not part of
|
||||
the game. It sits with the deployment, which is also where the canonical
|
||||
URL above points, so the two agree about which site this is.
|
||||
-->
|
||||
<meta property="og:image" content="https://games.jcoffey.dev/img/powers-social.png?v=2026-09-09a" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="Great Powers — the map of Europe in 1901, divided between seven colours"
|
||||
/>
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Great Powers — the classic negotiation game, in your browser" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Europe, 1901. Seven powers, thirty-four supply centres and no dice."
|
||||
/>
|
||||
<meta name="twitter:image" content="https://games.jcoffey.dev/img/powers-social.png?v=2026-09-09a" />
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "VideoGame",
|
||||
"name": "Great Powers",
|
||||
"url": "https://games.jcoffey.dev/powers/",
|
||||
"description": "A browser rebuild of the classic seven-power negotiation game. Europe in 1901, thirty-four supply centres, no luck of any kind: every unit is exactly as strong as every other, so nothing is taken without somebody agreeing to help take it. One human against six computer powers that propose, accept, refuse and break their word.",
|
||||
"genre": ["Strategy", "Board game", "Turn-based"],
|
||||
"gamePlatform": "Web browser",
|
||||
"applicationCategory": "GameApplication",
|
||||
"operatingSystem": "Any modern web browser",
|
||||
"playMode": "SinglePlayer",
|
||||
"numberOfPlayers": {
|
||||
"@type": "QuantitativeValue",
|
||||
"value": 1
|
||||
},
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "John Coffey",
|
||||
"url": "https://jcoffey.dev/"
|
||||
},
|
||||
"license": "https://www.gnu.org/licenses/agpl-3.0.html",
|
||||
"isAccessibleForFree": true,
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD",
|
||||
"availability": "https://schema.org/InStock"
|
||||
},
|
||||
"inLanguage": "en"
|
||||
}
|
||||
</script>
|
||||
|
||||
<!--
|
||||
The same three steps the way-back link offers, said in a form a crawler
|
||||
reads: this game sits under the games site, which sits under jcoffey.dev.
|
||||
-->
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "John Coffey",
|
||||
"item": "https://jcoffey.dev/"
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 2,
|
||||
"name": "Games",
|
||||
"item": "https://games.jcoffey.dev/"
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 3,
|
||||
"name": "Great Powers",
|
||||
"item": "https://games.jcoffey.dev/powers/"
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
What the page says before React has said anything. A crawler that does
|
||||
not run scripts, and a reader who has them turned off, both get the game
|
||||
described and a way onwards rather than an empty div.
|
||||
-->
|
||||
<noscript>
|
||||
<h1>Great Powers</h1>
|
||||
<p>
|
||||
Europe in 1901. Seven powers, thirty-four supply centres and no dice at
|
||||
all. Every unit is exactly as strong as every other, so one can never
|
||||
push another out of a province and two can — which means nothing
|
||||
happens on this board that somebody did not agree to help with. You
|
||||
play one power against six that negotiate, agree, and break their word.
|
||||
It needs JavaScript to play.
|
||||
</p>
|
||||
<p><a href="https://games.jcoffey.dev/">The rest of the games</a></p>
|
||||
<p><a href="https://github.com/Coffey-Labs/great-powers">Source, AGPL-3.0-or-later</a></p>
|
||||
</noscript>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+9
-1
@@ -26,5 +26,13 @@
|
||||
},
|
||||
"description": "A browser rebuild of the classic seven-power negotiation game, one human against six computer powers.",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "John Coffey"
|
||||
"author": "John Coffey",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Coffey-Labs/great-powers.git"
|
||||
},
|
||||
"homepage": "https://games.jcoffey.dev/powers/",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Coffey-Labs/great-powers/issues"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="Great Powers">
|
||||
<!-- Seven powers round a board in the seven colours the map uses, and the
|
||||
one supply centre in the middle that they are all looking at. Drawn
|
||||
rather than drafted in a tool, like every other mark in this
|
||||
repository. -->
|
||||
<rect width="64" height="64" fill="#e6d8ba"/>
|
||||
<g stroke="#2b2318" stroke-width="2.5">
|
||||
<circle cx="32" cy="10.5" r="5.5" fill="#d4736c"/>
|
||||
<circle cx="50.7" cy="21.3" r="5.5" fill="#e7a8c8"/>
|
||||
<circle cx="50.7" cy="42.7" r="5.5" fill="#89b4dd"/>
|
||||
<circle cx="32" cy="53.5" r="5.5" fill="#9d9d9d"/>
|
||||
<circle cx="13.3" cy="42.7" r="5.5" fill="#93cc9e"/>
|
||||
<circle cx="13.3" cy="21.3" r="5.5" fill="#b08fc4"/>
|
||||
<circle cx="32" cy="32" r="6.5" fill="#e8dc8c"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 836 B |
@@ -471,6 +471,7 @@ header select {
|
||||
color: #8c98ab;
|
||||
}
|
||||
.landing footer p { margin: 0 0 8px; }
|
||||
.landing footer .ways { font-size: 0.95rem; color: #cbd6e6; margin-bottom: 12px; }
|
||||
|
||||
/* ----------------------------------------------------------- giving up */
|
||||
|
||||
|
||||
@@ -105,6 +105,18 @@ export function Landing({
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p className="ways">
|
||||
{/*
|
||||
Absolute rather than "/", because this game is served from a
|
||||
subdirectory in production and from the root in development -- so
|
||||
the relative answer is right in one of those and points at the game
|
||||
itself in the other. The destination is a particular site, and
|
||||
naming it is the honest way to say so.
|
||||
*/}
|
||||
<a href="https://games.jcoffey.dev/">The rest of the games</a>
|
||||
{' · '}
|
||||
<a href="https://github.com/Coffey-Labs/great-powers">Source</a>
|
||||
</p>
|
||||
<p>
|
||||
Great Powers is free software under the{' '}
|
||||
<a href="https://www.gnu.org/licenses/agpl-3.0.html">GNU Affero General Public License,
|
||||
|
||||
+5
-12
@@ -4,21 +4,14 @@ import { defineConfig } from 'vite'
|
||||
// Vitest is left on its defaults on purpose: it already picks up *.test.ts and
|
||||
// nothing else, and importing `vitest/config` here drags in a second copy of
|
||||
// vite whose plugin types disagree with this one's.
|
||||
//
|
||||
// There is no dev proxy here, unlike this game's siblings. They post scores to
|
||||
// a shared service; this one has nothing to post. A game of Diplomacy is not a
|
||||
// number, and a leaderboard reading "reached eleven centres" would be a worse
|
||||
// thing to know about somebody than nothing at all.
|
||||
export default defineConfig({
|
||||
// Asset URLs are baked in at build time, so this has to match the path the
|
||||
// game is served from. Local development stays at the root.
|
||||
base: process.env.BASE_PATH ?? '/',
|
||||
plugins: [react()],
|
||||
server: {
|
||||
// The game talks to /api in every environment; in production that is
|
||||
// nginx in front of the shared scores container. In development, run
|
||||
// https://github.com/Coffey-Labs/games-scores alongside this -- or do not,
|
||||
// and the board will say so rather than breaking.
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: process.env.SCORES_TARGET ?? 'http://localhost:5184',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# The game is a static bundle. It is built here and served by nginx, in its own
|
||||
# container - it shares nothing with the rest of the site but the domain.
|
||||
#
|
||||
# Unlike its siblings this one talks to no service at all. There is no score to
|
||||
# post: a game of Diplomacy is not a number, and a leaderboard of "reached 11
|
||||
# centres" would be a worse thing to know about somebody than nothing.
|
||||
FROM node:26-alpine AS build
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY tsconfig*.json vite.config.ts index.html ./
|
||||
COPY public ./public
|
||||
COPY src ./src
|
||||
# Must match where the host proxies it, or the asset URLs will be wrong.
|
||||
ARG BASE_PATH=/powers/
|
||||
ENV BASE_PATH=${BASE_PATH}
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
COPY web/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
EXPOSE 8080
|
||||
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
|
||||
CMD wget -qO- http://127.0.0.1:8080/healthz >/dev/null || exit 1
|
||||
Reference in New Issue
Block a user