# Highland Games Studio — Website Marketing and devblog site for Highland Games Studio. _Where summits become worlds._ Built with **Next.js 16** (App Router), **React 19**, **Tailwind CSS 4** and **Framer Motion**. Devblog posts are authored in MDX and rendered with `next-mdx-remote`. ## Getting started ```bash npm install npm run dev ``` Open [http://localhost:3000](http://localhost:3000). ## Scripts | Command | Description | | ------------------ | ---------------------------------------- | | `npm run dev` | Start the dev server | | `npm run build` | Production build | | `npm run start` | Serve the production build | | `npm run lint` | Run ESLint | Asset generation scripts (require `sharp`, already a devDependency): ```bash node scripts/build-favicon.mjs # favicon + app icon from the mountain mark node scripts/build-wordmark.mjs # transparent wordmark from "Full Logo.png" ``` ## Project structure ```text app/ Routes, layouts, server actions, metadata (sitemap/robots/feed/OG) app/components/ui/ Design-system primitives (Section, SectionHeading, ButtonLink, Logo…) app/components/layout/ Navbar, Footer, SocialLinks app/components/game/ GameCard, ScreenshotGallery app/components/home/ Home hero app/components/devblog/ PostList app/components/forms/ ContactForm, Newsletter content/devblog/ Devblog posts as .mdx (frontmatter: title, date, excerpt, tags, game) lib/ Data + helpers (games registry, devblog loader, site config, formatting) public/ Static assets (studio logos, game art, hero video) scripts/ One-off asset build scripts ``` ### Content lives in `lib/`, never in a page - `lib/site.ts` — studio identity, navigation, contact channels, social accounts, brand assets. - `lib/games.ts` — the games registry: title, description, genres, key art, banner **and screenshots**. Pages read from these. That is what keeps the home page and `/games/[slug]` in sync — the featured block, the "From the workshop" gallery and the game page all render the same record. ### Design rule: no text over artwork The Emberwild key art is light cream and the in-game captures are bright green, so body text laid over them is unreadable at any gradient strength. Cards therefore put the image on one panel and the text on a solid `bg-surface` panel; the game page shows the banner alone and places the title underneath. Keep it that way when adding new layouts. ## Adding a game or new screenshots Drop the files in `public/image/Games//`, then add or edit the entry in `lib/games.ts`: ```ts { slug: "emberwild", title: "Emberwild", status: "in-development", // in-development | coming-soon | released tagline: "One readable line.", description: "Full pitch, shown in the About block.", genres: ["Survival", "Crafting"], releaseWindow: "TBA", keyArt: "/image/Games/Emberwild/Cover.png", // illustration, shown contained banner: "/image/Games/Emberwild/Capture_1.png", // wide in-game shot, page banner screenshots: [ { src: "/image/Games/Emberwild/Capture_2.png", caption: "Autumn treeline" }, ], } ``` The gallery, the home page teaser, `/games`, the sitemap and the JSON-LD all pick it up. ## Configuration Copy `.env.example` to `.env.local` and fill in the values. - **Site URL** — set `NEXT_PUBLIC_SITE_URL` per environment (used by metadata, sitemap, robots, RSS and JSON-LD). Defaults to `https://highlandgamesstudio.com` — see `lib/site.ts`. - **Contact form (Resend)** — the contact form sends email via [Resend](https://resend.com). Set `RESEND_API_KEY`, `CONTACT_TO_EMAIL` (who receives messages) and `CONTACT_FROM_EMAIL` (sender, must be on a domain verified in Resend). Without these, the form returns a friendly error instead of sending. See `app/actions/contact.ts`. ## Adding a devblog post Create a file in `content/devblog/`, e.g. `2026-06-01-my-post.mdx`: ```mdx --- title: "My post title" date: "2026-06-01" excerpt: "One-line summary for listings and SEO." author: "Highland Games Studio" tags: ["devlog"] game: "emberwild" --- Your MDX content here. ``` ## Deployment Pushes to `master` deploy to the Hetzner server via the Gitea Actions workflow (`.gitea/workflows/deploy.yml`), which SSHes in, pulls, builds, and restarts the PM2 process (`highland`, in `/var/www/highland`). Requires these **repo secrets** (Gitea → repo → Settings → Actions → Secrets): `SSH_HOST`, `SSH_USER`, `SSH_PORT`, `SSH_PRIVATE_KEY`. Server-side prerequisites (one-time): - A registered Gitea `act_runner` with the `ubuntu-latest` label. - `/var/www/highland` is a clone of this repo whose `origin` can pull the **private** Gitea repo non-interactively (deploy key or token in the remote URL). - Node + `pm2` installed, with the app running as the `highland` process. ## Known TODOs See `TODO.md` for the full list. The two blockers are the 84 MB hero video (`public/Video/Header_Video.mp4`) and the social links, which are still unpublished (`SOCIAL_LINKS` in `lib/site.ts` — entries with `href: null` are hidden rather than rendered as dead `#` links).