refactor: remove pre-Studio website; wire arikigame.com into monorepo
Build tinqs-git / build (push) Waiting to run
Deploy arikigame.com / deploy (push) Waiting to run

Delete web/landing (legacy tinqs-ltd/website Next.js). Add web/arikigame with static public site and deploy-arikigame.yml (S3 + CloudFront). Link Ariki from tinqs.com logged-out home. Fix build.yml to trigger on main.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-22 08:03:43 +01:00
parent 4a9469cf65
commit 7482278ae1
46 changed files with 137 additions and 10908 deletions
+2 -2
View File
@@ -2,9 +2,9 @@ name: Build tinqs-git
on:
push:
branches: [tinqs/main]
branches: [main]
pull_request:
branches: [tinqs/main]
branches: [main]
jobs:
build:
+34
View File
@@ -0,0 +1,34 @@
name: Deploy arikigame.com
on:
push:
branches: [main]
paths:
- 'web/arikigame/**'
- '.gitea/workflows/deploy-arikigame.yml'
jobs:
deploy:
runs-on: host
steps:
- uses: actions/checkout@v4
- name: Deploy static site to S3 + CloudFront
env:
S3_BUCKET: arikigame.com
CF_DISTRIBUTION: EDMY8TXLTDXLQ
run: |
set -e
SRC="web/arikigame/public"
if [ ! -d "$SRC" ]; then
echo "Missing $SRC — nothing to deploy"
exit 1
fi
echo "Syncing $SRC → s3://${S3_BUCKET}/"
aws s3 sync "$SRC" "s3://${S3_BUCKET}/" --delete \
--cache-control "public, max-age=300"
echo "Invalidating CloudFront ${CF_DISTRIBUTION}..."
aws cloudfront create-invalidation \
--distribution-id "${CF_DISTRIBUTION}" \
--paths "/*"
echo "OK arikigame.com deployed from tinqs/studio/web/arikigame/public"
+5
View File
@@ -97,6 +97,11 @@
</div>
</div>
<div style="max-width: 900px; margin: 0 auto 48px; padding: 0 24px; text-align: center;">
<p style="color: var(--color-text-light); font-size: 0.95rem; margin-bottom: 12px;">We build our own game on this platform.</p>
<a href="https://arikigame.com" style="display: inline-block; padding: 14px 28px; background: #1a1510; border: 1px solid #c9935a55; border-radius: 8px; color: #e0b87a; font-weight: 600; text-decoration: none; font-size: 1rem;">Ariki &mdash; Polynesian survival colony sim &rarr;</a>
</div>
<div class="tinqs-contact">
<h2>Get Early Access</h2>
<p>We are onboarding studios one at a time. Tell us about your project.</p>
+29
View File
@@ -0,0 +1,29 @@
# arikigame.com — Ariki game website
Player-facing marketing site for **Ariki**, the game we build with Tinqs Studio.
| | |
|---|---|
| **URL** | https://arikigame.com |
| **Source** | `tinqs/studio/web/arikigame/` (this folder) |
| **Deploy** | Gitea Actions — `.gitea/workflows/deploy-arikigame.yml` on push to `main` |
## Layout
```
web/arikigame/
├── public/ ← static files served at arikigame.com (today)
│ └── index.html
├── README.md
└── (future) Next.js app — replace `public/` when `ozan/arikigame` is merged here
```
## What was removed
The old **tinqs-ltd/website** Next.js app lived at `web/landing/` before monorepo consolidation. That was the pre-Studio company site — deleted 22 May 2026. Platform marketing is **tinqs.com** (`templates/home.tmpl` in the Gitea fork). Game marketing is **arikigame.com** (this folder).
## Deploy
Push changes under `web/arikigame/` to `main`. CI syncs `public/` to S3 and invalidates CloudFront.
Confirm bucket name in workflow env if deploy fails — see `deploy-arikigame.yml`.
+67
View File
@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ariki — Polynesian Survival Colony Sim</title>
<meta name="description" content="Ariki is a Polynesian survival colony sim built by Tinqs using Tinqs Studio — git, LFS, AI agents, and Godot in one workflow.">
<link rel="canonical" href="https://arikigame.com/">
<meta property="og:type" content="website">
<meta property="og:url" content="https://arikigame.com/">
<meta property="og:title" content="Ariki — Polynesian Survival Colony Sim">
<meta property="og:description" content="Survive, build, and lead your colony across the Pacific. Built with Tinqs Studio.">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
background: #0a0a0f;
color: #e8e4df;
line-height: 1.6;
min-height: 100vh;
}
.wrap { max-width: 720px; margin: 0 auto; padding: 80px 24px 120px; text-align: center; }
h1 { font-size: 2.8rem; font-weight: 300; letter-spacing: 0.02em; margin-bottom: 8px; }
h1 span { color: #c9935a; }
.tagline { font-size: 1.15rem; color: #9e9890; margin-bottom: 40px; }
.cta {
display: inline-block;
padding: 14px 32px;
background: #c9935a;
color: #0a0a0f;
text-decoration: none;
border-radius: 6px;
font-weight: 600;
margin: 8px;
}
.cta.secondary {
background: transparent;
border: 1px solid #c9935a55;
color: #e0b87a;
}
.cta:hover { background: #e0b87a; color: #0a0a0f; }
.cta.secondary:hover { border-color: #c9935a; }
.studio {
margin-top: 64px;
padding-top: 32px;
border-top: 1px solid #1e1c24;
font-size: 0.9rem;
color: #837d76;
}
.studio a { color: #c9935a; }
</style>
</head>
<body>
<div class="wrap">
<h1><span>Ariki</span></h1>
<p class="tagline">Polynesian survival colony sim — built with Tinqs Studio</p>
<p style="color: #9e9890; margin-bottom: 32px; max-width: 520px; margin-left: auto; margin-right: auto;">
Lead your people across the Pacific. Ceremony, mana, and colony life — in development at Tinqs.
</p>
<a class="cta" href="https://store.steampowered.com/app/0" rel="noopener">Wishlist on Steam</a>
<a class="cta secondary" href="https://tinqs.com">Built on Tinqs Studio</a>
<p class="studio">
Made by <a href="https://tinqs.com">Tinqs</a> — we use our own platform to ship this game.
</p>
</div>
</body>
</html>
After
-43
View File
@@ -1,43 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
.vercel
-76
View File
@@ -1,76 +0,0 @@
# Can's Tasks — Marketing & Steam Store Page
Owner: Can Gunaslan
Created: 2026-04-17
Context: Onboarding meeting with Ozan
---
## Phase 1: Setup Complete (Week of Apr 17)
- [ ] Verify all tools working: Claude Code, Team Tool, Gitea, AWS CLI
- [ ] Install ffmpeg: `winget install Gyan.FFmpeg`
- [ ] Set AWS credentials: `aws configure` (keys from Ozan, account 149751500842, region eu-west-1)
- [ ] Read this repo's codebase (Next.js static site, deployed to S3+CloudFront at tinqs.com)
## Phase 2: Research & Prep (Apr 1721)
- [ ] Read the GDD v3.1 (`tinqs-ltd/docs/GDD.md`) — understand Ariki's identity, features, positioning
- [ ] Study Steam Store page requirements:
- Capsule images: Header (460x215), Small (231x87), Main (616x353), Hero (3840x1240), Logo (no fixed size)
- Screenshots: minimum 5, recommended 10+ (1920x1080)
- Trailer: at least one, MP4, 1080p minimum
- Short description: max 300 characters
- About the game: detailed HTML description
- System requirements: min + recommended specs
- Tags/genres: survival, strategy, simulation, multiplayer, early access
- [ ] Research comparable Steam pages for tone and layout:
- Dawn of Man
- Valheim
- Going Medieval
- Palworld
- Farthest Frontier
- [ ] Note what works: trailer style, screenshot composition, description structure, tag selection
## Phase 3: Drafts (Apr 2125)
- [ ] Draft short description (300 chars) — Ozan + Ozlem review
- [ ] Draft "About This Game" section — HTML formatted, key features, lore hook
- [ ] Draft system requirements (check with Ozan for actual specs)
- [ ] Create asset checklist for Ozlem (what images/video she needs to produce)
- [ ] Identify Early Access specific copy needs (why EA, current state, roadmap, community)
## Phase 4: Steamworks Setup (with Ozan)
- [ ] Ozan: create Ariki app in Steamworks
- [ ] Ozan: add Can to Steamworks partner group
- [ ] Can: enter store page copy into Steamworks
- [ ] Ozlem: upload visual assets (capsules, screenshots)
- [ ] All: review "Coming Soon" page before publishing
- [ ] Publish "Coming Soon" page — start collecting wishlists
## Phase 5: Marketing Plan (ongoing)
- [ ] Create marketing timeline (pre-launch, launch, post-launch)
- [ ] Identify content creation opportunities (devlogs, trailers, community posts)
- [ ] Set up Steam community hub (discussions, announcements)
- [ ] Plan social media cadence (X, Discord, YouTube)
---
## Resources
| Resource | Location |
|----------|----------|
| GDD v3.1 | `tinqs-ltd/docs/GDD.md` |
| Website repo | `tinqs-ltd/website` (this repo) |
| Steamworks dashboard | https://partner.steamgames.com |
| Steam developer page | https://store.steampowered.com/developer/tinqsstudio |
| Bot dashboard | https://bot.arikigame.com |
| Indie Marketing Guide | `tinqs-ltd/docs/Indie_Game_Marketing_Guide.md` |
## Contacts
- **Ozan** — CTO, technical specs, Steamworks admin
- **Ozlem** — CEO/Designer, visual direction, brand approval
- **Jeremy** — PM, backlog, coordination
-1
View File
@@ -1 +0,0 @@
@AGENTS.md
-120
View File
@@ -1,120 +0,0 @@
# Meeting Log — 2026-04-17 (Ozan + Can)
**Date:** 2026-04-17, 16:1418:41 BST
**Attendees:** Ozan Bozkurt (CTO), Can Gunaslan, Ozlem (joined briefly near end)
**Category:** Meeting — Can onboarding + marketing kickoff
---
## Summary
### Part 1: Tool Setup (~16:1417:08)
Ozan walked Can through the full Tinqs developer toolchain setup on Can's machine (CANG):
1. **MCP vs CLI explanation** — Ozan explained why the team is moving from MCP (Model Context Protocol) to CLI-based tools. MCP fills up AI context and is expensive. CLI is cheaper, more predictable.
2. **Tinqs Team Tool demo** — Showed Can:
- Screenshot capture (Unity, Chrome, Team Tool itself)
- Dual transcription engines (ElevenLabs Scribe + Web Speech) side by side
- Live transcript view
- Daily cost tracking
- S3 cloud upload of transcripts
- How agents read transcripts + screenshots to write code and create PRs
3. **AWS cloud stack** — Briefly discussed. Ozan uses AWS (preference), Melih uses GCP. Each uses what they know.
4. **Claude Code setup** — Helped Can:
- Install Claude Code via npm
- Log in to Anthropic Team (can@tinqs.com)
- Navigate terminal basics (cd, ls, tab completion)
- Open Cursor and connect Claude Code
- Session naming (`/rename`, `/color`)
5. **Setup wizard** (v2.7.0) — Ran the install wizard on Can's machine. Hit issues with:
- ffmpeg missing (Team Tool dependency) — **fixed: added to wizard v2.8.0**
- AWS CLI winget install failure
- Claude Code auth flow with multiple browser tabs open
6. **Gitea token** — Created Git Studio token for Can's machine (tinqs-team-tool-can-cang)
7. **Team Tool** — Installed, pinned to taskbar, showed live transcription working
### Part 2: Marketing Discussion (~17:0717:10)
- Ozan mentioned wanting to discuss marketing with Can and Ozlem
- Can has availability until 18:00
- Can requested a Monday follow-up for topics where he needs Ozan's input
- Ozlem was called to join for the marketing conversation
- Can mentioned he interviewed a **game designer/PM from Bahcesehir University** for a potential hire (intelligence layer — game jam planning + marketing plan generation)
### Part 3: Steamworks Setup (~during meeting, Ozan side)
While chatting with Can, Ozan set up:
- Steam account: **tinqsstudio** (ozan)
- Steamworks developer registration: **Tinqs Limited**
- Fee paid: **£73.99**
- Bank details: Monzo USD via WorldPay
- W-8BEN-E tax interview started (Corporation, UK)
- Website updated with Steam developer page link
---
## Action Items
### Can — Immediate
- [ ] Complete Claude Code setup (verify `claude --version` works)
- [ ] Pin Team Tool to taskbar, verify transcription works
- [ ] Save Gitea token securely
- [ ] Install ffmpeg (`winget install Gyan.FFmpeg`)
- [ ] Set up AWS credentials (`aws configure` — keys from Ozan)
### Can — Marketing (this week)
- [ ] Review the `tinqs-ltd/website` repo (Next.js, static site on S3+CloudFront)
- [ ] Review the Ariki GDD (v3.1) in `tinqs-ltd/docs/GDD.md`
- [ ] Prepare Steam Store page asset checklist (capsule images, screenshots, trailer specs)
- [ ] Draft "Coming Soon" store page copy (short description, about section, system requirements)
- [ ] Research comparable indie games on Steam for positioning (Dawn of Man, Valheim, Going Medieval)
- [ ] Coordinate with Ozlem on visual assets / brand direction
### Can — Follow-up Monday
- [ ] Schedule follow-up with Ozan for topics needing his input
- [ ] Bring marketing plan outline for discussion
### Ozan
- [x] Setup wizard v2.8.0 — added ffmpeg install
- [x] Steamworks account registered (tinqsstudio)
- [x] Website Steam link updated and pushed
- [x] Steamworks setup saved to website + devops repos
- [ ] Complete W-8BEN-E tax interview
- [ ] Add team members to Steamworks (ozlem, can, jeremy, uygar, melih)
- [ ] Create "Ariki" app in Steamworks dashboard
### Ozlem
- [ ] Lead Steam Store page visual direction (capsule art, screenshots, trailer)
- [ ] Review store copy draft from Can
---
## Decisions Made
1. **Steam account name:** tinqsstudio
2. **Payment method:** Monzo USD account via WorldPay
3. **Entity type for tax:** Corporation (UK Limited)
4. **Can's role:** Marketing first, using Claude Code + Team Tool for coordination
5. **Potential hire:** Game designer/PM from Bahcesehir Uni — Can interviewing, may bring on for intelligence layer (game jam + marketing plan generation)
---
## Notes
- Can's Windows username is `django` (not `can`) — the setup wizard detected from known users map
- Can's machine: CANG
- Can is also working on GameCaster (his own company) — will use Claude subscription for both, Ozan is fine with it
- Team Tool v4.9.6 requires ffmpeg — was missing from setup wizard, now fixed in v2.8.0
- Claude Code auth gets confused with multiple browser tabs — tell new users to close all but one Chrome tab
-36
View File
@@ -1,36 +0,0 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
-56
View File
@@ -1,56 +0,0 @@
# Steamworks — Tinqs Limited
Registered: 2026-04-17
## Account
| Field | Value |
|-------|-------|
| **Steam username** | ozan |
| **Partner name** | tinqsstudio |
| **Legal entity** | Tinqs Limited |
| **Developer page** | https://store.steampowered.com/developer/tinqsstudio |
| **Partner site** | https://partner.steamgames.com |
| **Contact email** | ozan@tinqs.com |
| **Fee paid** | £73.99 |
## Payment Details (WorldPay)
| Field | Value |
|-------|-------|
| **Bank Account Holder** | Tinqs Limited |
| **Bank Name** | MONZO |
| **Bank SWIFT Code** | TRWIGB2LXXX |
| **Sort Code** | 040004 |
| **Payee IBAN** | ************9095 |
| **Account Type** | Checking |
| **Currency** | USD |
| **Bank City** | London |
| **Bank Postal Code** | EC2A 2AG |
| **Bank Country** | United Kingdom |
## Tax Information
- Status: **Not yet filed** (W-8BEN-E required for UK company)
- Provider: TaxIdentity by Lilaham
## Team Access
Add via Users & Permissions on partner.steamgames.com:
| Email | Role |
|-------|------|
| ozan@tinqs.com | Owner (admin) |
| ozlem@tinqs.com | Owner (admin) |
| can@tinqs.com | Developer |
| jeremy@tinqs.com | PM |
| uygar@tinqs.com | Tester (view only) |
| melih@tinqs.com | Consultant (view only) |
## Next Steps
1. Complete W-8BEN-E tax interview (required before publishing)
2. Create "Ariki" app in Steamworks
3. Add team members via Users & Permissions
4. Prepare store page assets (capsules, screenshots, trailer — Ozlem leads)
5. Publish "Coming Soon" page for wishlists
@@ -1,140 +0,0 @@
---
title: "How We Use Cursor with DeepSeek (and Why It's Faster and Cheaper)"
date: "2026-05-20"
author: "Ozan Bozkurt"
excerpt: "We built an OpenAI-compatible proxy that routes Cursor through DeepSeek with caching, backoff, and a shared agent bus. Here's how it works and why our three-person team ships at startup speed."
tags: ["cursor", "deepseek", "ai", "devtools", "go", "proxy"]
---
We're a three-person indie game studio building [Ariki](https://arikigame.com) — a Polynesian survival colony sim. We use AI agents heavily: five people on the team, each with their own agent pair, running 8-hour days. Our total spend last week? **$56.**
This isn't luck. It's a deliberate architecture choice built around a simple idea: **Cursor talks OpenAI, DeepSeek answers OpenAI — and everything in between is a single Go binary a friend can run.**
---
## The Problem
Cursor's agent mode is powerful, but default provider pricing adds up fast with heavy usage. Our team runs multiple concurrent agent sessions daily — coding, reviewing, testing, planning. At standard rates, that would easily hit hundreds per month.
We wanted:
- **Cheap inference** — DeepSeek's pricing is drastically lower for comparable quality
- **Caching** — Cursor strips `reasoning_content` on replay, so we cache it ourselves
- **Queue resilience** — DeepSeek rate-limits aggressively; we needed exponential backoff with a job queue
- **Team bus** — multiple agents on one machine should share context (voice transcripts, decisions, traces)
- **No vendor lock-in** — swap the backend provider without changing anything in Cursor
---
## The Solution: A Local OpenAI-Compatible Proxy
We wrote a single Go binary (~11 MB) that sits between Cursor and DeepSeek:
```
Cursor IDE ──POST /v1/chat/completions──▶ localhost:8787 ──▶ api.deepseek.com
(OpenAI format) (our proxy) (DeepSeek API)
```
Cursor thinks it's talking to OpenAI. Our proxy translates, queues, caches, and forwards. From Cursor's perspective, nothing changes — you point it at `http://127.0.0.1:8787/v1` and keep working.
### What the proxy does
**1. Accepts OpenAI-compatible chat completions** — the exact format Cursor sends. No middleware, no adapters. Drop-in.
**2. Forwards to DeepSeek with exponential backoff** — when DeepSeek returns 429 (rate limit) or 503 (overloaded), the proxy retries with increasing delays. Agents never see the error.
**3. Caches reasoning content** — DeepSeek sends a `reasoning_content` field alongside the final response. Cursor strips this on replay (the "reasoning" section disappears when you scroll back). We cache it separately so agents can reference previous reasoning chains.
**4. System prompt hot-reload** — the agent's system prompt ("You are a senior software engineer working inside Cursor IDE...") lives in a file, reloadable without restarting.
**5. Job queue with Redis** — multiple agents hitting DeepSeek simultaneously? The queue serializes requests, applies backoff globally, and broadcasts results via SSE to all waiting clients.
**6. Shared agent bus** — the same localhost server that proxies inference also hosts our Team Tool: voice transcription, screenshot capture, trace search, and session management. Every agent on the machine reads the same state.
---
## The Numbers
Running DeepSeek V4 Pro through our proxy:
| Item | Cost |
|------|------|
| 5 team members × 2 agents | 10 concurrent sessions |
| 8 hours/day, 7 days | 56 agent-hours/week |
| **Total spend (1 week)** | **$56** |
Compare that to default provider pricing for the same workload — we're saving roughly 80-90%.
---
## How to Set It Up (For Your Team)
We're packaging this as an open-source Go binary. Until the repo is public, here's the pattern:
### 1. Build the proxy
```bash
git clone https://git.arikigame.com/tinqs-ltd/cursor-harness.git
cd cursor-harness/cmd/proxy
go build -o cursor-proxy .
```
### 2. Configure
```bash
export DEEPSEEK_API_KEY="sk-your-key-here"
export LISTEN=":8787"
./cursor-proxy
```
### 3. Point Cursor at it
In Cursor settings, set **OpenAI Base URL** to:
```
http://127.0.0.1:8787/v1
```
That's it. Cursor now routes through DeepSeek.
### 4. Optional: Redis for multi-agent queueing
```bash
export REDIS_URL="redis://localhost:6379"
export CACHE_BACKEND="redis"
```
Without Redis, the proxy uses in-memory caching and direct forwarding — fine for a single user.
---
## What We Learned
**Go is the right language for this.** A single static binary with zero runtime dependencies. Friends on Windows, Mac, or Linux run the same binary. No Node.js, no Python venv, no Docker required (unless you want Redis).
**Caching reasoning content matters more than you'd think.** DeepSeek's reasoning chains are the most valuable part of the output — they show _why_ the model made a decision. Without caching, Cursor's replay strips them and you lose institutional knowledge.
**The agent bus is the real multiplier.** Having every agent on the machine read the same voice transcripts, screenshots, and traces means you never ask "what did you want?" — the agents already know what you just said.
**Queueing prevents thundering herd.** When five agents all fire requests at once, the queue absorbs the spikes, applies backoff once (not five times), and broadcasts results. Dramatically fewer 429s.
---
## What's Next
We're extracting the proxy and agent bus into a proper open-source package (`cursor-harness`). Friends will be able to:
- Run the proxy as a single binary (no Git Studio account needed)
- Plug in any OpenAI-compatible backend (DeepSeek, Groq, Together, local Ollama)
- Add the Team Tool bus for shared agent context
- Contribute back — MIT license, public repo on Git Studio
The goal isn't to sell anything. It's to share what we built because we think three-person teams should be able to ship at startup speed without startup burn.
---
**Questions?** Find us at [arikigame.com](https://arikigame.com) or on the repo at [git.arikigame.com/tinqs-ltd/cursor-harness](https://git.arikigame.com/tinqs-ltd/cursor-harness).
---
*Ozan Bozkurt is CTO at Tinqs, building Ariki — a Polynesian survival colony sim. He writes about AI tooling, game architecture, and shipping fast with small teams.*
@@ -1,141 +0,0 @@
---
title: "Why We Forked Gitea — Large File Storage for Game Dev"
date: "2026-05-20"
author: "Ozan Bozkurt"
excerpt: "Game repos aren't like web repos. A single art asset can be 500MB. GitHub doesn't cut it. Here's why we forked Gitea, added LFS on S3, and why every game studio should own their git infra."
tags: ["gitea", "lfs", "gamedev", "devops", "git", "go"]
---
Our main game repo is **37 GB**. A single character model can be 500 MB. A Unity scene with all dependencies pushes past 2 GB. GitHub's LFS gives you 1 GB free and charges by the gig after that. For a three-person indie studio shipping a procedural colony sim, that math doesn't work.
So we forked Gitea. Here's why, how, and what we're building on top of it.
---
## The Problem: Game Assets Don't Fit in Normal Git
Web development repos are kilobytes of text. Game development repos are gigabytes of binary. A typical Ariki build includes:
| Asset Type | Typical Size |
|-----------|-------------|
| Character model (FBX + textures) | 200500 MB |
| Environment (terrain, foliage, materials) | 13 GB |
| Audio (FMOD project, stems, ambience) | 500 MB2 GB |
| Unity/Godot scene with dependencies | 500 MB2 GB |
| Build artifacts (per platform) | 15 GB |
That's before you count iteration. We iterate fast — multiple builds per day, new art landing every few hours from the concept pipeline, audio revisions from the sound engineer. Git without LFS would balloon the `.git` folder to hundreds of gigabytes and make `git clone` a coffee-break-length operation.
**GitHub LFS pricing** at our scale would cost hundreds per month — and that's before you factor in the 1 GB free tier being exhausted by a single texture pack.
---
## The Solution: Self-Hosted Gitea with S3 LFS
We run [Gitea](https://about.gitea.com/) — the open-source, self-hosted Git service — on a $13/month AWS Lightsail instance (2 vCPU, 2 GB RAM, 60 GB SSD). LFS blobs go to S3 (`tinqs-gitea-lfs` bucket in eu-west-1), costing roughly $1.60/month for storage and transfer.
```
Developer machine Git Studio (Lightsail)
│ │
│ git push (LFS tracked) │
├──────────────────────────────────────►│
│ ├── git objects → SQLite (local SSD)
│ ├── LFS blobs → S3 (tinqs-gitea-lfs)
│ │
│ git clone / git pull │
◄───────────────────────────────────────┤
│ (LFS blobs streamed from S3) │
```
Total cost: **~$14.60/month** for unlimited LFS storage. For comparison, GitHub Teams is $4/user/month plus LFS data charges — at our scale, that's roughly **10x more expensive**.
---
## Why We Forked It
Gitea is great out of the box, but game development has specific needs that the upstream project doesn't prioritize:
### 1. Asset preview in the browser
When a designer pushes a new `.fbx` or `.png`, the team should be able to see it in the browser — not download it, find the right viewer, and open it. We're building a web-based 3D asset viewer (Three.js + WebGL) that renders models, textures, and materials directly in the repo browser.
```
Repo view → Click a .fbx file → Three.js renders it inline
→ Rotate, zoom, inspect materials
→ "Approved" button for art review
```
### 2. LFS UX improvements
Upstream Gitea's LFS UI is functional but sparse. We're adding:
- **Per-file LFS tracking status** — see at a glance which files are tracked, their sizes, and when they were last pushed
- **LFS storage breakdown** — which asset types consume the most space
- **Batch operations** — migrate multiple files into/out of LFS in one click
### 3. Team Tool integration
Our [Team Tool](https://bot.arikigame.com/guide/team-tool-ui) (the local agent bus we use for voice transcription, screenshots, and agent coordination) will integrate directly with the forked Gitea:
- Push notifications land as breadcrumbs in the live transcript session
- Agent chat can query repo state and file contents
- Build status appears in the shared timeline
### 4. Platform SSO
Long-term: OAuth2-based single sign-on across Git Studio, the Team Tool, admin panel, and the Ariki game itself. One account, everywhere.
---
## The Architecture
Our fork lives at `tinqs-ltd/tinqs-git` on Git Studio. It tracks upstream Gitea via periodic rebase:
```
upstream (github.com/go-gitea/gitea) ──rebase──► main
├── tinqs/main (our customizations)
├── tinqs/phase-1 (branding)
├── tinqs/phase-2 (LFS UX)
├── tinqs/phase-3 (platform integration)
└── tinqs/phase-8 (platform auth)
```
We modify:
- **Frontend** (Vue + TypeScript templates) — asset viewer, LFS UI, branding
- **Backend** (Go) — new API routes, LFS improvements, auth modules
- **Database** (SQLite via CGo) — compiled into the binary, no external DB
We **don't touch**:
- Core Git operations, webhook dispatch, user management, migration — those stay upstream-clean for easy rebasing.
---
## Why Every Game Studio Should Own Their Git
Game development has unique constraints that hosted platforms don't (and shouldn't) solve:
1. **Asset size is orders of magnitude larger** — a web app's entire repo fits in one game asset. LFS is non-negotiable, and cloud LFS pricing adds up fast.
2. **Binary diffs don't compress** — every new version of a `.fbx` or `.psd` is a whole new blob. You need LFS to keep the repo cloneable.
3. **Iteration speed demands local or near-local git** — waiting 30 seconds for `git push` kills flow. Self-hosted on the same continent means LAN-speed pushes.
4. **Custom workflows** — art review in-browser, CI that understands Unity/Godot builds, automated asset pipeline triggers on push. These are game-specific and don't belong in a general-purpose git host.
5. **Cost predictability** — $14.60/month, flat. No per-gigabyte surprises when the art team ships a big update.
---
## What's Next
We're actively developing `tinqs-git` (our Gitea fork) in the open. The repo is on [Git Studio](https://git.arikigame.com/tinqs-ltd/tinqs-git) — currently private during development, going public when the asset viewer ships. The fork stays AGPL (upstream Gitea's license), and we'll upstream improvements that make sense for the broader community.
If you're a game dev team hitting the limits of hosted Git, self-hosting Gitea with S3 LFS is a weekend project that pays for itself in the first month.
---
**Questions?** Find us at [arikigame.com](https://arikigame.com) or [tinqs.com/blog](https://tinqs.com/blog).
---
*Ozan Bozkurt is CTO at Tinqs, building Ariki — a Polynesian survival colony sim. He writes about game dev infrastructure, AI tooling, and shipping with small teams.*
-25
View File
@@ -1,25 +0,0 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Static export — <a> and <img> are fine, Next.js Image/Link don't apply
{
rules: {
"@next/next/no-html-link-for-pages": "off",
"@next/next/no-img-element": "off",
},
},
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
]),
]);
export default eslintConfig;
-5
View File
@@ -1,5 +0,0 @@
# tinqs.com
Tinqs company website — Next.js static export, S3 + CloudFront.
**Repo:** [git.arikigame.com/tinqs-ltd/website](https://git.arikigame.com/tinqs-ltd/website)
-8
View File
@@ -1,8 +0,0 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
output: "export",
trailingSlash: true,
};
export default nextConfig;
-8156
View File
File diff suppressed because it is too large Load Diff
-29
View File
@@ -1,29 +0,0 @@
{
"name": "website",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
},
"dependencies": {
"gray-matter": "^4.0.3",
"next": "^16.2.3",
"react": "19.2.4",
"react-dom": "19.2.4",
"react-markdown": "^10.1.0",
"remark-gfm": "^4.0.1"
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "^16.2.3",
"tailwindcss": "^4",
"typescript": "^5"
}
}
-7
View File
@@ -1,7 +0,0 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;
-4
View File
@@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="6" fill="#0a0a0f"/>
<text x="16" y="23" font-family="Georgia, serif" font-size="18" font-weight="bold" fill="#c9935a" text-anchor="middle">T</text>
</svg>

Before

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 658 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

-3
View File
@@ -1,3 +0,0 @@
User-agent: *
Allow: /
Sitemap: https://www.tinqs.com/sitemap.xml
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://www.tinqs.com</loc>
<changefreq>weekly</changefreq>
<priority>1</priority>
</url>
</urlset>
-80
View File
@@ -1,80 +0,0 @@
import { getAllSlugs, getPostBySlug } from "@/lib/posts";
import { notFound } from "next/navigation";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import Link from "next/link";
import type { Metadata } from "next";
export function generateStaticParams() {
return getAllSlugs().map((slug) => ({ slug }));
}
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const post = getPostBySlug(slug);
if (!post) return { title: "Not Found" };
return {
title: `${post.title} — Tinqs Blog`,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
type: "article",
authors: [post.author],
publishedTime: post.date,
},
};
}
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = getPostBySlug(slug);
if (!post) notFound();
return (
<main className="blog-post">
<div className="blog-post__inner">
<Link href="/blog" className="blog-post__back">
&larr; All posts
</Link>
<header className="blog-post__header">
<h1 className="blog-post__title">{post.title}</h1>
<div className="blog-post__meta">
<span className="blog-post__author">{post.author}</span>
<time className="blog-post__date">
{new Date(post.date).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</time>
</div>
<div className="blog-post__tags">
{post.tags.map((tag) => (
<span key={tag} className="blog-post__tag">
{tag}
</span>
))}
</div>
</header>
<div className="blog-post__content prose">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{post.content}
</ReactMarkdown>
</div>
</div>
</main>
);
}
-54
View File
@@ -1,54 +0,0 @@
import { getAllPosts } from "@/lib/posts";
import Link from "next/link";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Blog — Tinqs",
description:
"Writing about AI tooling, game dev, and shipping fast with small teams.",
};
export default function BlogPage() {
const posts = getAllPosts();
return (
<main className="blog-list">
<div className="blog-list__inner">
<h1 className="blog-list__title">Blog</h1>
<p className="blog-list__subtitle">
AI tooling, game dev, and shipping fast with small teams.
</p>
<div className="blog-list__grid">
{posts.map((post) => (
<Link
key={post.slug}
href={`/blog/${post.slug}`}
className="blog-card"
>
<article>
<div className="blog-card__meta">
{post.tags.map((tag) => (
<span key={tag} className="blog-card__tag">
{tag}
</span>
))}
<time className="blog-card__date">
{new Date(post.date).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</time>
</div>
<h2 className="blog-card__title">{post.title}</h2>
<p className="blog-card__excerpt">{post.excerpt}</p>
<span className="blog-card__author">{post.author}</span>
</article>
</Link>
))}
</div>
</div>
</main>
);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because it is too large Load Diff
-124
View File
@@ -1,124 +0,0 @@
import type { Metadata } from "next";
import { Inter, Libre_Baskerville } from "next/font/google";
import "./globals.css";
const inter = Inter({
variable: "--f-body",
subsets: ["latin"],
display: "swap",
});
const libreBaskerville = Libre_Baskerville({
variable: "--f-head",
subsets: ["latin"],
weight: ["400", "700"],
style: ["normal", "italic"],
display: "swap",
});
export const metadata: Metadata = {
title: "Tinqs — Polynesian survival game studio | London",
description:
"London indie studio making Ariki — a Polynesian survival colony sim. Rise from castaway to chieftain across a Pacific archipelago. Co-op multiplayer, Early Access.",
keywords: [
"Tinqs",
"indie game studio",
"Ariki",
"Polynesian survival game",
"island colony builder",
"co-op survival sim",
"Pacific island exploration",
"tribe management",
"village builder",
"Early Access",
"Steam",
],
authors: [{ name: "Tinqs Limited" }],
openGraph: {
type: "website",
url: "https://www.tinqs.com/",
title: "Tinqs — Polynesian survival game studio",
description:
"The tribe is the game. Indie studio from London crafting Ariki.",
siteName: "Tinqs",
images: [
{
url: "https://www.tinqs.com/img/og-cover.jpg",
width: 1200,
height: 630,
alt: "Ariki — a Polynesian survival colony sim",
},
],
},
twitter: {
card: "summary_large_image",
title: "Tinqs — Polynesian survival game studio",
description:
"The tribe is the game. Indie studio from London crafting Ariki.",
images: ["https://www.tinqs.com/img/og-cover.jpg"],
},
metadataBase: new URL("https://www.tinqs.com"),
alternates: {
canonical: "/",
},
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" className={`${inter.variable} ${libreBaskerville.variable}`}>
<head>
<link rel="icon" type="image/svg+xml" href="/img/favicon.svg" />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "Organization",
name: "Tinqs Limited",
url: "https://www.tinqs.com",
logo: "https://www.tinqs.com/img/favicon.svg",
foundingDate: "2020",
description:
"Indie game studio crafting Ariki — a Polynesian survival colony sim set in a Pacific archipelago.",
email: "hello@tinqs.com",
address: {
"@type": "PostalAddress",
addressLocality: "London",
addressCountry: "UK",
},
sameAs: ["https://arikigame.com"],
}),
}}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "VideoGame",
name: "Ariki",
alternateName: "Ariki",
url: "https://arikigame.com",
description:
"A Polynesian survival colony sim. Rise from castaway to chieftain across a Pacific archipelago.",
genre: ["Survival", "Colony Sim", "Action RPG"],
gamePlatform: ["PC", "PlayStation", "Xbox"],
applicationCategory: "Game",
operatingSystem: ["Windows", "macOS"],
author: {
"@type": "Organization",
name: "Tinqs Limited",
url: "https://www.tinqs.com",
},
}),
}}
/>
</head>
<body>{children}</body>
</html>
);
}
-383
View File
@@ -1,383 +0,0 @@
"use client";
import { useEffect } from "react";
/* ── SVG Icon Components ── */
function SteamIcon() {
return (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M11.979 0C5.678 0 .511 4.86.022 11.037l6.432 2.658c.545-.371 1.203-.59 1.912-.59.063 0 .125.004.188.006l2.861-4.142V8.91c0-2.495 2.028-4.524 4.524-4.524 2.494 0 4.524 2.031 4.524 4.527s-2.03 4.525-4.524 4.525h-.105l-4.076 2.911c0 .052.004.105.004.159 0 1.875-1.515 3.396-3.39 3.396-1.635 0-3.016-1.173-3.331-2.727L.436 15.27C1.862 20.307 6.486 24 11.979 24c6.627 0 11.999-5.373 11.999-12S18.605 0 11.979 0zM7.54 18.21l-1.473-.61c.262.543.714.999 1.314 1.25 1.297.539 2.793-.076 3.332-1.375.263-.63.264-1.319.005-1.949s-.75-1.121-1.377-1.383c-.624-.26-1.29-.249-1.878-.03l1.523.63c.956.4 1.409 1.5 1.009 2.455-.397.957-1.497 1.41-2.454 1.012H7.54zm11.415-9.303c0-1.662-1.353-3.015-3.015-3.015-1.665 0-3.015 1.353-3.015 3.015 0 1.665 1.35 3.015 3.015 3.015 1.663 0 3.015-1.35 3.015-3.015zm-5.273-.005c0-1.252 1.013-2.266 2.265-2.266 1.249 0 2.266 1.014 2.266 2.266 0 1.251-1.017 2.265-2.266 2.265-1.253 0-2.265-1.014-2.265-2.265z" />
</svg>
);
}
function DiscordIcon() {
return (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M20.32 4.37a19.8 19.8 0 0 0-4.88-1.52.07.07 0 0 0-.08.04c-.21.38-.45.87-.61 1.26a18.27 18.27 0 0 0-5.5 0 12.64 12.64 0 0 0-.63-1.26.08.08 0 0 0-.08-.04 19.74 19.74 0 0 0-4.88 1.52.07.07 0 0 0-.03.03C1.07 8.7.32 12.88.7 17.01a.08.08 0 0 0 .03.06 19.9 19.9 0 0 0 5.99 3.03.08.08 0 0 0 .08-.03c.46-.63.87-1.3 1.22-2a.08.08 0 0 0-.04-.11 13.1 13.1 0 0 1-1.87-.9.08.08 0 0 1 0-.13c.13-.09.25-.19.37-.29a.08.08 0 0 1 .08-.01c3.93 1.8 8.18 1.8 12.07 0a.08.08 0 0 1 .08 0c.12.1.25.2.37.3a.08.08 0 0 1 0 .12c-.6.35-1.22.65-1.88.9a.08.08 0 0 0-.04.11c.36.7.77 1.37 1.22 2a.08.08 0 0 0 .08.03 19.83 19.83 0 0 0 6-3.03.08.08 0 0 0 .04-.05c.45-4.69-.76-8.83-3.2-12.61a.06.06 0 0 0-.04-.03ZM8.02 14.33c-1.09 0-1.98-1-1.98-2.23s.87-2.23 1.98-2.23c1.12 0 2 1.01 1.98 2.23 0 1.23-.87 2.23-1.98 2.23Zm7.32 0c-1.09 0-1.98-1-1.98-2.23s.87-2.23 1.98-2.23c1.12 0 2 1.01 1.98 2.23 0 1.23-.87 2.23-1.98 2.23Z" />
</svg>
);
}
function XIcon() {
return (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M18.24 2.25h3.31l-7.23 8.26 8.51 11.24h-6.66l-5.21-6.82-5.97 6.82H1.68l7.73-8.84L1.17 2.25h6.83l4.72 6.24 5.52-6.24Zm-1.16 17.52h1.83L7.08 4.13H5.11l11.97 15.64Z" />
</svg>
);
}
function YouTubeIcon() {
return (
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M23.5 6.19a3.02 3.02 0 0 0-2.12-2.14C19.5 3.5 12 3.5 12 3.5s-7.5 0-9.38.55A3.02 3.02 0 0 0 .5 6.19 31.56 31.56 0 0 0 0 12a31.56 31.56 0 0 0 .5 5.81 3.02 3.02 0 0 0 2.12 2.14c1.88.55 9.38.55 9.38.55s7.5 0 9.38-.55a3.02 3.02 0 0 0 2.12-2.14A31.56 31.56 0 0 0 24 12a31.56 31.56 0 0 0-.5-5.81ZM9.55 15.57V8.43L15.82 12l-6.27 3.57Z" />
</svg>
);
}
/* ── Page Component ── */
export default function Home() {
useEffect(() => {
// Nav scroll effect
const nav = document.getElementById("nav");
const handleScroll = () => {
nav?.classList.toggle("nav--scrolled", window.scrollY > 60);
};
window.addEventListener("scroll", handleScroll, { passive: true });
// Mobile menu toggle
const burger = document.getElementById("navBurger");
const mobileMenu = document.getElementById("mobileMenu");
const toggleMenu = () => {
const open = mobileMenu?.classList.toggle("mobile-menu--open");
burger?.classList.toggle("nav__burger--open", open ?? false);
document.body.style.overflow = open ? "hidden" : "";
};
burger?.addEventListener("click", toggleMenu);
mobileMenu?.querySelectorAll("a").forEach((link) => {
link.addEventListener("click", () => {
mobileMenu?.classList.remove("mobile-menu--open");
burger?.classList.remove("nav__burger--open");
document.body.style.overflow = "";
});
});
// Fade-in on scroll
const faders = document.querySelectorAll(
".about, .pillars, .gallery__row, .world, .signup"
);
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (e.isIntersecting) e.target.classList.add("visible");
});
},
{ threshold: 0.1 }
);
faders.forEach((el) => observer.observe(el));
// Parallax on gallery images
const parallaxEls = document.querySelectorAll(
".gallery__img-wrap--parallax img"
);
const handleParallax = () => {
parallaxEls.forEach((img) => {
const rect = (img as HTMLElement).parentElement!.getBoundingClientRect();
const speed = 0.08;
const yOffset = (rect.top - window.innerHeight / 2) * speed;
(img as HTMLElement).style.transform = `translateY(${yOffset}px) scale(1.08)`;
});
};
window.addEventListener("scroll", handleParallax, { passive: true });
return () => {
window.removeEventListener("scroll", handleScroll);
window.removeEventListener("scroll", handleParallax);
burger?.removeEventListener("click", toggleMenu);
observer.disconnect();
};
}, []);
return (
<>
{/* ── NAV ── */}
<nav className="nav" id="nav">
<a href="/" className="nav__logo" aria-label="Tinqs home">
<span className="nav__wordmark">TINQS</span>
</a>
<div className="nav__center">
<a href="#game" className="nav__link">Games</a>
<a href="#about" className="nav__link">About</a>
<a href="/blog" className="nav__link">Blog</a>
<a href="#careers" className="nav__link">Careers</a>
<a href="#signup" className="nav__link">Contact</a>
</div>
<div className="nav__socials">
<a href="https://store.steampowered.com/developer/tinqsstudio" target="_blank" rel="noopener noreferrer" className="nav__social-icon" aria-label="Steam" title="Steam"><SteamIcon /></a>
<a href="#" className="nav__social-icon" aria-label="Discord" title="Discord"><DiscordIcon /></a>
<a href="#" className="nav__social-icon" aria-label="X" title="X"><XIcon /></a>
<a href="#" className="nav__social-icon" aria-label="YouTube" title="YouTube"><YouTubeIcon /></a>
</div>
<button className="nav__burger" aria-label="Open menu" id="navBurger">
<span></span><span></span><span></span>
</button>
</nav>
{/* ── MOBILE MENU ── */}
<div className="mobile-menu" id="mobileMenu">
<a href="#game" className="mobile-menu__link">Games</a>
<a href="#about" className="mobile-menu__link">About</a>
<a href="/blog" className="mobile-menu__link">Blog</a>
<a href="#careers" className="mobile-menu__link">Careers</a>
<a href="#signup" className="mobile-menu__link">Contact</a>
</div>
{/* ── HERO ── */}
<header className="hero" id="hero">
<div className="hero__bg" aria-hidden="true" />
<div className="hero__vignette" aria-hidden="true" />
<div className="hero__content">
<h1 className="hero__title">The tribe is the game.</h1>
<p className="hero__subtitle">Rise from castaway to chieftain</p>
<span className="hero__badge">Early Access &middot; Coming Soon</span>
<br />
<a href="#game" className="hero__cta">Explore</a>
</div>
<div className="hero__scroll-hint" aria-hidden="true">
<span></span>
</div>
</header>
{/* ── ABOUT ── */}
<section className="about" id="about">
<div className="about__inner">
<span className="section-label">The Studio</span>
<h2 className="about__title">
Indie game-makers from London.
<br />
Three people, one world, one voyage.
</h2>
<p className="about__text">
Tinqs is a three-person indie team crafting Ariki &mdash; a survival
colony sim set in a Polynesian-inspired archipelago, where every island
hides spirits, resources, and stories waiting to be uncovered.
</p>
<a href="#careers" className="about__hiring">We&rsquo;re Hiring &rarr;</a>
</div>
</section>
{/* ── CAREERS ── */}
<section className="careers" id="careers">
<div className="careers__inner">
<h2 className="careers__title">Join the crew</h2>
<p className="careers__text">
We&rsquo;re looking for people who care about craft. If you want to help
build a world worth exploring, get in touch.
</p>
<a href="mailto:hello@tinqs.com" className="careers__cta">Apply Now</a>
<span className="careers__email">or email hello@tinqs.com</span>
</div>
</section>
{/* ── THREE PILLARS ── */}
<section className="pillars" id="game">
<span className="section-label">Ariki</span>
<h2 className="pillars__title">Survive. Build. Explore.</h2>
<div className="pillars__grid">
<article className="pillar">
<div className="pillar__img-wrap">
<img
src="/img/scene-survive-cooking.png"
alt="Tribal character cooking fish under a thatched shelter at golden hour"
loading="lazy"
/>
</div>
<h3 className="pillar__name">Survive</h3>
<p className="pillar__desc">
Forage, hunt, and cook to keep your tribe alive. Every resource
matters on an uncharted island.
</p>
</article>
<article className="pillar">
<div className="pillar__img-wrap">
<img
src="/img/scene-build-settlement.png"
alt="Aerial view of a thriving island settlement with huts, farms, and palm trees"
loading="lazy"
/>
</div>
<h3 className="pillar__name">Build</h3>
<p className="pillar__desc">
Grow from a makeshift camp to a thriving colony. Hundreds of
villagers, working as one.
</p>
</article>
<article className="pillar">
<div className="pillar__img-wrap">
<img
src="/img/scene-explore-canoe.png"
alt="Character standing beside an outrigger canoe on a tropical beach"
loading="lazy"
/>
</div>
<h3 className="pillar__name">Explore</h3>
<p className="pillar__desc">
Craft a canoe and voyage to new islands. Each one holds unique
spirits, dangers, and rewards.
</p>
</article>
</div>
<span className="pillars__meta">
PC &middot; Console &middot; Co-op Multiplayer &middot; Early Access
</span>
<br />
<a href="https://arikigame.com" className="pillars__game-link">
Explore Ariki &rarr;
</a>
</section>
{/* ── CINEMATIC GALLERY ── */}
<section className="gallery" id="gallery">
<div className="gallery__row">
<div className="gallery__img-wrap gallery__img-wrap--parallax">
<img
src="/img/gallery/gallery-rice-paddy.png"
alt="Character walking through terraced rice fields with lotus ponds and bamboo"
loading="lazy"
/>
</div>
<div className="gallery__text">
<span className="section-label">Shape the Land</span>
<p className="gallery__caption">
Terrace the hillsides. Cultivate rice paddies. Transform wild
jungle into a flourishing homeland &mdash; from Micronesian huts to
Balinese stone temples.
</p>
</div>
</div>
<div className="gallery__row gallery__row--reverse">
<div className="gallery__img-wrap gallery__img-wrap--parallax">
<img
src="/img/gallery/gallery-hillside-path.png"
alt="Character on a dirt trail with palm trees and terraced fields at golden hour"
loading="lazy"
/>
</div>
<div className="gallery__text">
<span className="section-label">Chart the Unknown</span>
<p className="gallery__caption">
Every island is a new frontier. Trek through palm forests, volcanic
ridges, and hidden valleys to discover resources and ancient shrines.
</p>
</div>
</div>
<div className="gallery__row">
<div className="gallery__img-wrap gallery__img-wrap--parallax">
<img
src="/img/gallery/gallery-forest-overlook.png"
alt="Character with a staff looking over a forested village from a rocky overlook"
loading="lazy"
/>
</div>
<div className="gallery__text">
<span className="section-label">Lead Your People</span>
<p className="gallery__caption">
You are the chief. Guide your tribe through storms, spirit
encounters, and the long voyage toward a place to call home.
</p>
</div>
</div>
</section>
{/* ── THE WORLD ── */}
<section className="world" id="world">
<div className="world__bg" aria-hidden="true" />
<div className="world__inner">
<span className="section-label">The World</span>
<h2 className="world__title">
Inspired by the islands of Southeast Asia and the Pacific
</h2>
<p className="world__text">
Voyaging cultures, living spirits, and an archipelago procedurally
different every time you land. Where the Pacific meets something
ancient.
</p>
<div className="world__map">
<img
src="/img/world-map-gold.jpg"
alt="Isleborn volcanic island with village, waterfalls, and turquoise ocean"
loading="lazy"
/>
</div>
</div>
</section>
{/* ── CONNECT ── */}
<section className="connect" id="connect">
<span className="section-label">Community</span>
<h2 className="connect__title">Follow the voyage</h2>
<div className="connect__links">
<a href="https://store.steampowered.com/developer/tinqsstudio" target="_blank" rel="noopener noreferrer" className="connect__link">
<SteamIcon />
<span className="connect__link-label">Steam</span>
</a>
<a href="#" className="connect__link">
<DiscordIcon />
<span className="connect__link-label">Discord</span>
</a>
<a href="#" className="connect__link">
<XIcon />
<span className="connect__link-label">X</span>
</a>
<a href="#" className="connect__link">
<YouTubeIcon />
<span className="connect__link-label">YouTube</span>
</a>
</div>
</section>
{/* ── SIGNUP ── */}
<section className="signup" id="signup">
<div className="signup__bg" aria-hidden="true" />
<div className="signup__overlay" aria-hidden="true" />
<div className="signup__inner">
<span className="section-label">Stay in the Loop</span>
<h2 className="signup__title">Early Access is coming.</h2>
<p className="signup__subtitle">Be first to know.</p>
<form className="signup__form" action="#" method="POST">
<input
type="email"
name="email"
placeholder="your@email.com"
required
aria-label="Email address"
/>
<button type="submit" className="btn">Stay Close</button>
</form>
<p className="signup__note">No spam. Only milestones.</p>
</div>
</section>
{/* ── FOOTER ── */}
<footer className="footer">
<div className="footer__inner">
<span className="footer__wordmark">TINQS</span>
<div className="footer__links">
<a href="#game">Games</a>
<a href="#about">About</a>
<a href="/blog">Blog</a>
<a href="#careers">Careers</a>
<a href="mailto:hello@tinqs.com">hello@tinqs.com</a>
<a href="/press">Press Kit</a>
</div>
<p className="footer__copy">
Tinqs Limited &mdash; London, est. 2020
</p>
</div>
</footer>
</>
);
}
-147
View File
@@ -1,147 +0,0 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Tinqs Team Tool — Setup",
description: "Install the Tinqs Team Tool for live transcription and team collaboration.",
};
export default function TeamToolPage() {
return (
<div style={{ background: "#0a0a0a", minHeight: "100vh", color: "#e0e0e0", fontFamily: "system-ui, -apple-system, sans-serif" }}>
<main style={{ maxWidth: 720, margin: "0 auto", padding: "40px 24px", lineHeight: 1.6 }}>
<div style={{ marginBottom: 32 }}>
<h1 style={{ fontSize: 28, fontWeight: 700, marginBottom: 4, color: "#fff" }}>
Tinqs Team Tool
</h1>
<p style={{ color: "#888", fontSize: 14, margin: 0 }}>
Live transcription, dual-engine (Edge + ElevenLabs), speaker diarization, screenshots, team chat.
</p>
</div>
{/* ── Prerequisites ── */}
<Section title="Before you start" color="#ef4444">
<ol style={ol}>
<li>
<strong style={{ color: "#fca5a5" }}>Uninstall Tailscale</strong> we no longer use it.
<br />
<span style={{ color: "#6b7280", fontSize: 12 }}>
Windows: Settings &rarr; Apps &rarr; Tailscale &rarr; Uninstall.
</span>
</li>
<li>
<strong>Clear old bash/shell records</strong> remove any Tailscale references from your shell config:
<Code>{`# PowerShell: remove Tailscale from PATH if added manually
# Check your $PROFILE for any Tailscale lines:
notepad $PROFILE`}</Code>
</li>
<li>
<strong>Restart your machine</strong> clean slate before installing.
</li>
</ol>
</Section>
{/* ── Install ── */}
<Section title="Install the Team Tool" color="#fbbf24">
<h3 style={h3}>Option A One-line install (recommended)</h3>
<p style={{ color: "#9ca3af", fontSize: 13, marginBottom: 8 }}>
Open <strong>PowerShell</strong> and paste:
</p>
<Code>{`irm https://bot.arikigame.com/setup-windows.ps1 | iex`}</Code>
<h3 style={h3}>Option B Manual download</h3>
<ol style={ol}>
<li>
Download{" "}
<A href="https://tinqs-cli-releases.s3.eu-west-1.amazonaws.com/team-tool/latest/TinqsTeamTool.exe">
TinqsTeamTool.exe
</A>
</li>
<li>Install ffmpeg: <Code inline>{`winget install ffmpeg`}</Code></li>
<li>Run <code style={code}>TinqsTeamTool.exe</code></li>
</ol>
</Section>
{/* ── Setup Token ── */}
<Section title="Get your token" color="#60a5fa">
<ol style={ol}>
<li>
Go to{" "}
<A href="https://git.arikigame.com/user/settings/applications">
Git Studio &rarr; Settings &rarr; Applications
</A>
</li>
<li>Token Name: <code style={code}>tinqs-team-tool-yourname</code></li>
<li>Permissions: select all &rarr; Generate Token</li>
<li>Copy the token you{"'"}ll need it on first launch</li>
</ol>
<p style={{ color: "#6b7280", fontSize: 12, marginTop: 8 }}>
Can{"'"}t access Git Studio? Ask Ozan or Jeremy for an invite.
</p>
</Section>
{/* ── What you get ── */}
<Section title="What the tool does" color="#10b981">
<ul style={{ ...ol, listStyleType: "none", paddingLeft: 0 }}>
<li style={{ marginBottom: 6 }}><span style={{ color: "#fbbf24" }}>Left column</span> Edge Web Speech (real-time, free, instant)</li>
<li style={{ marginBottom: 6 }}><span style={{ color: "#60a5fa" }}>Right column</span> ElevenLabs Scribe V2 (5s chunks, speaker IDs, higher accuracy)</li>
<li style={{ marginBottom: 6 }}>Screenshot bar capture Unity, Chrome, Full Screen, or All Windows</li>
<li style={{ marginBottom: 6 }}>Language switch English / Turkish</li>
<li style={{ marginBottom: 6 }}>Auto-updates checks every 5 min, one-click apply</li>
</ul>
</Section>
{/* ── Troubleshooting ── */}
<Section title="Troubleshooting" color="#f97316">
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
<tbody>
<Tr label="No mic">Windows Settings &rarr; Privacy &rarr; Microphone &rarr; Allow</Tr>
<Tr label="No blue column">ELEVENLABS_API_KEY not set ask Ozan or Jeremy</Tr>
<Tr label="ffmpeg error"><code style={code}>winget install ffmpeg</code> then restart</Tr>
<Tr label="Won't start">Kill old instance: <code style={code}>taskkill /f /im TinqsTeamTool.exe</code></Tr>
<Tr label="Can't reach Git Studio">Make sure you can open <A href="https://git.arikigame.com">git.arikigame.com</A> in your browser</Tr>
</tbody>
</table>
</Section>
<div style={{ borderTop: "1px solid #222", marginTop: 40, paddingTop: 16, color: "#444", fontSize: 12 }}>
Tinqs Team Tool &middot;{" "}
<A href="https://git.arikigame.com/tinqs-ltd/bot">source</A> &middot;{" "}
<A href="https://git.arikigame.com/tinqs-ltd/bot/issues">issues</A> &middot;{" "}
<A href="https://www.tinqs.com">tinqs.com</A>
</div>
</main>
</div>
);
}
function Section({ title, color, children }: { title: string; color: string; children: React.ReactNode }) {
return (
<section style={{ marginBottom: 20, padding: "14px 18px", background: "#111", borderRadius: 8, border: "1px solid #1a1a1a" }}>
<h2 style={{ fontSize: 15, fontWeight: 600, color, margin: "0 0 10px" }}>{title}</h2>
{children}
</section>
);
}
function Code({ children, inline }: { children: string; inline?: boolean }) {
if (inline) return <code style={{ background: "#1a1a22", padding: "1px 6px", borderRadius: 4, fontSize: 12, color: "#c084fc" }}>{children}</code>;
return (
<pre style={{ background: "#0d0d0d", border: "1px solid #222", borderRadius: 6, padding: "10px 14px", fontSize: 13, overflowX: "auto", fontFamily: "ui-monospace, Consolas, monospace", color: "#c084fc" }}>
{children}
</pre>
);
}
function A({ href, children }: { href: string; children: React.ReactNode }) {
return <a href={href} target="_blank" rel="noopener noreferrer" style={{ color: "#60a5fa", textDecoration: "none" }}>{children}</a>;
}
function Tr({ label, children }: { label: string; children: React.ReactNode }) {
return (
<tr style={{ borderBottom: "1px solid #1a1a1a" }}>
<td style={{ padding: "6px 10px 6px 0", color: "#fbbf24", fontWeight: 600, whiteSpace: "nowrap", verticalAlign: "top" }}>{label}</td>
<td style={{ padding: "6px 0", color: "#9ca3af" }}>{children}</td>
</tr>
);
}
const ol: React.CSSProperties = { paddingLeft: 20, color: "#9ca3af", fontSize: 13 };
const h3: React.CSSProperties = { fontSize: 14, fontWeight: 600, color: "#d1d5db", margin: "14px 0 6px" };
const code: React.CSSProperties = { background: "#1a1a22", padding: "1px 6px", borderRadius: 4, fontSize: 12, color: "#c084fc" };
-67
View File
@@ -1,67 +0,0 @@
import fs from "fs";
import path from "path";
import matter from "gray-matter";
const postsDirectory = path.join(process.cwd(), "content", "blog");
export interface BlogPost {
slug: string;
title: string;
date: string;
author: string;
excerpt: string;
tags: string[];
content: string;
}
export function getAllPosts(): BlogPost[] {
const filenames = fs.readdirSync(postsDirectory);
const posts = filenames
.filter((f) => f.endsWith(".md"))
.map((filename) => {
const slug = filename.replace(/\.md$/, "");
const fullPath = path.join(postsDirectory, filename);
const fileContents = fs.readFileSync(fullPath, "utf8");
const { data, content } = matter(fileContents);
return {
slug,
title: data.title ?? slug,
date: data.date ?? "",
author: data.author ?? "Tinqs",
excerpt: data.excerpt ?? "",
tags: data.tags ?? [],
content,
} as BlogPost;
})
.sort((a, b) => (a.date < b.date ? 1 : -1));
return posts;
}
export function getPostBySlug(slug: string): BlogPost | null {
try {
const fullPath = path.join(postsDirectory, `${slug}.md`);
const fileContents = fs.readFileSync(fullPath, "utf8");
const { data, content } = matter(fileContents);
return {
slug,
title: data.title ?? slug,
date: data.date ?? "",
author: data.author ?? "Tinqs",
excerpt: data.excerpt ?? "",
tags: data.tags ?? [],
content,
};
} catch {
return null;
}
}
export function getAllSlugs(): string[] {
const filenames = fs.readdirSync(postsDirectory);
return filenames
.filter((f) => f.endsWith(".md"))
.map((f) => f.replace(/\.md$/, ""));
}
-42
View File
@@ -1,42 +0,0 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts",
"**/*.mts"
],
"exclude": [
"node_modules"
]
}