chore: bootstrap ashen realms monorepo

This commit is contained in:
Bastian Wagner
2026-08-18 17:01:21 +02:00
commit 9e92731645
50 changed files with 24031 additions and 0 deletions

9
.env.example Normal file
View File

@@ -0,0 +1,9 @@
NODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://ashen:ashen@localhost:5432/ashen_realms
JWT_ACCESS_SECRET=change-me
JWT_REFRESH_SECRET=change-me-too
JWT_ACCESS_TTL=15m
JWT_REFRESH_TTL=30d

15
.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
node_modules/
dist/
coverage/
.env
.env.*
!.env.example
.angular/
.cache/
.idea/
.vscode/
.DS_Store

56
apps/api/.gitignore vendored Normal file
View File

@@ -0,0 +1,56 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

4
apps/api/.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

98
apps/api/README.md Normal file
View File

@@ -0,0 +1,98 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Project setup
```bash
$ npm install
```
## Compile and run the project
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Run tests
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Deployment
When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information.
If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps:
```bash
$ npm install -g @nestjs/mau
$ mau deploy
```
With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure.
## Resources
Check out a few resources that may come in handy when working with NestJS:
- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework.
- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy).
- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/).
- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks.
- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com).
- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com).
- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs).
- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com).
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).

View File

@@ -0,0 +1,35 @@
// @ts-check
import eslint from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import globals from 'globals';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{
ignores: ['eslint.config.mjs'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
eslintPluginPrettierRecommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
sourceType: 'commonjs',
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn',
"prettier/prettier": ["error", { endOfLine: "auto" }],
},
},
);

8
apps/api/nest-cli.json Normal file
View File

@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

71
apps/api/package.json Normal file
View File

@@ -0,0 +1,71 @@
{
"name": "@ashen-realms/api",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^11.0.1",
"@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.18.0",
"@nestjs/cli": "^11.0.0",
"@nestjs/schematics": "^11.0.0",
"@nestjs/testing": "^11.0.1",
"@types/express": "^5.0.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.0.0",
"@types/supertest": "^7.0.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^17.0.0",
"jest": "^30.0.0",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

View File

@@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}

View File

@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

8
apps/api/src/main.ts Normal file
View File

@@ -0,0 +1,8 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

View File

@@ -0,0 +1,29 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { App } from 'supertest/types';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication<App>;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
afterEach(async () => {
await app.close();
});
});

View File

@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

25
apps/api/tsconfig.json Normal file
View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"resolvePackageJsonExports": true,
"esModuleInterop": true,
"isolatedModules": true,
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2023",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false
}
}

17
apps/web/.editorconfig Normal file
View File

@@ -0,0 +1,17 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
ij_typescript_use_double_quotes = false
[*.md]
max_line_length = off
trim_trailing_whitespace = false

44
apps/web/.gitignore vendored Normal file
View File

@@ -0,0 +1,44 @@
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-files for more about ignoring files.
# Compiled output
/dist
/tmp
/out-tsc
/bazel-out
# Node
/node_modules
npm-debug.log
yarn-error.log
# IDEs and editors
.idea/
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# Visual Studio Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/mcp.json
.history/*
# Miscellaneous
/.angular/cache
.sass-cache/
/connect.lock
/coverage
/libpeerconnection.log
testem.log
/typings
__screenshots__/
# System files
.DS_Store
Thumbs.db

12
apps/web/.prettierrc Normal file
View File

@@ -0,0 +1,12 @@
{
"printWidth": 100,
"singleQuote": true,
"overrides": [
{
"files": "*.html",
"options": {
"parser": "angular"
}
}
]
}

59
apps/web/README.md Normal file
View File

@@ -0,0 +1,59 @@
# Web
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.6.
## Development server
To start a local development server, run:
```bash
ng serve
```
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
## Code scaffolding
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
```bash
ng generate component component-name
```
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
```bash
ng generate --help
```
## Building
To build the project run:
```bash
ng build
```
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
## Running unit tests
To execute unit tests with the [Vitest](https://vitest.dev/) test runner, use the following command:
```bash
ng test
```
## Running end-to-end tests
For end-to-end (e2e) testing, run:
```bash
ng e2e
```
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
## Additional Resources
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.

78
apps/web/angular.json Normal file
View File

@@ -0,0 +1,78 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"cli": {
"packageManager": "npm"
},
"newProjectRoot": "projects",
"projects": {
"web": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"browser": "src/main.ts",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
{
"glob": "**/*",
"input": "public"
}
],
"styles": [
"src/styles.scss"
]
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kB",
"maximumError": "1MB"
},
{
"type": "anyComponentStyle",
"maximumWarning": "4kB",
"maximumError": "8kB"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"configurations": {
"production": {
"buildTarget": "web:build:production"
},
"development": {
"buildTarget": "web:build:development"
}
},
"defaultConfiguration": "development"
},
"test": {
"builder": "@angular/build:unit-test"
}
}
}
}
}

32
apps/web/package.json Normal file
View File

@@ -0,0 +1,32 @@
{
"name": "@ashen-realms/web",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"packageManager": "npm@11.12.1",
"dependencies": {
"@angular/common": "^21.2.0",
"@angular/compiler": "^21.2.0",
"@angular/core": "^21.2.0",
"@angular/forms": "^21.2.0",
"@angular/platform-browser": "^21.2.0",
"@angular/router": "^21.2.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0"
},
"devDependencies": {
"@angular/build": "^21.2.6",
"@angular/cli": "^21.2.6",
"@angular/compiler-cli": "^21.2.0",
"jsdom": "^28.0.0",
"prettier": "^3.8.1",
"typescript": "~5.9.2",
"vitest": "^4.0.8"
}
}

BIN
apps/web/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,11 @@
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes)
]
};

344
apps/web/src/app/app.html Normal file
View File

@@ -0,0 +1,344 @@
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content below * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * Delete the template below * * * * * * * * * -->
<!-- * * * * * * * to get started with your project! * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<style>
:host {
--bright-blue: oklch(51.01% 0.274 263.83);
--electric-violet: oklch(53.18% 0.28 296.97);
--french-violet: oklch(47.66% 0.246 305.88);
--vivid-pink: oklch(69.02% 0.277 332.77);
--hot-red: oklch(61.42% 0.238 15.34);
--orange-red: oklch(63.32% 0.24 31.68);
--gray-900: oklch(19.37% 0.006 300.98);
--gray-700: oklch(36.98% 0.014 302.71);
--gray-400: oklch(70.9% 0.015 304.04);
--red-to-pink-to-purple-vertical-gradient: linear-gradient(
180deg,
var(--orange-red) 0%,
var(--vivid-pink) 50%,
var(--electric-violet) 100%
);
--red-to-pink-to-purple-horizontal-gradient: linear-gradient(
90deg,
var(--orange-red) 0%,
var(--vivid-pink) 50%,
var(--electric-violet) 100%
);
--pill-accent: var(--bright-blue);
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
display: block;
height: 100dvh;
}
h1 {
font-size: 3.125rem;
color: var(--gray-900);
font-weight: 500;
line-height: 100%;
letter-spacing: -0.125rem;
margin: 0;
font-family: "Inter Tight", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";
}
p {
margin: 0;
color: var(--gray-700);
}
main {
width: 100%;
min-height: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
box-sizing: inherit;
position: relative;
}
.angular-logo {
max-width: 9.2rem;
}
.content {
display: flex;
justify-content: space-around;
width: 100%;
max-width: 700px;
margin-bottom: 3rem;
}
.content h1 {
margin-top: 1.75rem;
}
.content p {
margin-top: 1.5rem;
}
.divider {
width: 1px;
background: var(--red-to-pink-to-purple-vertical-gradient);
margin-inline: 0.5rem;
}
.pill-group {
display: flex;
flex-direction: column;
align-items: start;
flex-wrap: wrap;
gap: 1.25rem;
}
.pill {
display: flex;
align-items: center;
--pill-accent: var(--bright-blue);
background: color-mix(in srgb, var(--pill-accent) 5%, transparent);
color: var(--pill-accent);
padding-inline: 0.75rem;
padding-block: 0.375rem;
border-radius: 2.75rem;
border: 0;
transition: background 0.3s ease;
font-family: var(--inter-font);
font-size: 0.875rem;
font-style: normal;
font-weight: 500;
line-height: 1.4rem;
letter-spacing: -0.00875rem;
text-decoration: none;
white-space: nowrap;
}
.pill:hover {
background: color-mix(in srgb, var(--pill-accent) 15%, transparent);
}
.pill-group .pill:nth-child(6n + 1) {
--pill-accent: var(--bright-blue);
}
.pill-group .pill:nth-child(6n + 2) {
--pill-accent: var(--electric-violet);
}
.pill-group .pill:nth-child(6n + 3) {
--pill-accent: var(--french-violet);
}
.pill-group .pill:nth-child(6n + 4),
.pill-group .pill:nth-child(6n + 5),
.pill-group .pill:nth-child(6n + 6) {
--pill-accent: var(--hot-red);
}
.pill-group svg {
margin-inline-start: 0.25rem;
}
.social-links {
display: flex;
align-items: center;
gap: 0.73rem;
margin-top: 1.5rem;
}
.social-links path {
transition: fill 0.3s ease;
fill: var(--gray-400);
}
.social-links a:hover svg path {
fill: var(--gray-900);
}
@media screen and (max-width: 650px) {
.content {
flex-direction: column;
width: max-content;
}
.divider {
height: 1px;
width: 100%;
background: var(--red-to-pink-to-purple-horizontal-gradient);
margin-block: 1.5rem;
}
}
</style>
<main class="main">
<div class="content">
<div class="left-side">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 982 239"
fill="none"
class="angular-logo"
>
<g clip-path="url(#a)">
<path
fill="url(#b)"
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
/>
<path
fill="url(#c)"
d="M388.676 191.625h30.849L363.31 31.828h-35.758l-56.215 159.797h30.848l13.174-39.356h60.061l13.256 39.356Zm-65.461-62.675 21.602-64.311h1.227l21.602 64.311h-44.431Zm126.831-7.527v70.202h-28.23V71.839h27.002v20.374h1.392c2.782-6.71 7.2-12.028 13.255-15.956 6.056-3.927 13.584-5.89 22.503-5.89 8.264 0 15.465 1.8 21.684 5.318 6.137 3.518 10.964 8.673 14.319 15.382 3.437 6.71 5.074 14.81 4.992 24.383v76.175h-28.23v-71.92c0-8.019-2.046-14.237-6.219-18.819-4.173-4.5-9.819-6.791-17.102-6.791-4.91 0-9.328 1.063-13.174 3.272-3.846 2.128-6.792 5.237-9.001 9.328-2.046 4.009-3.191 8.918-3.191 14.728ZM589.233 239c-10.147 0-18.82-1.391-26.103-4.091-7.282-2.7-13.092-6.382-17.511-10.964-4.418-4.582-7.528-9.655-9.164-15.219l25.448-6.136c1.145 2.372 2.782 4.663 4.991 6.954 2.209 2.291 5.155 4.255 8.837 5.81 3.683 1.554 8.428 2.291 14.074 2.291 8.019 0 14.647-1.964 19.884-5.81 5.237-3.845 7.856-10.227 7.856-19.064v-22.665h-1.391c-1.473 2.946-3.601 5.892-6.383 9.001-2.782 3.109-6.464 5.645-10.965 7.691-4.582 2.046-10.228 3.109-17.101 3.109-9.165 0-17.511-2.209-25.039-6.545-7.446-4.337-13.42-10.883-17.757-19.474-4.418-8.673-6.628-19.473-6.628-32.565 0-13.091 2.21-24.301 6.628-33.383 4.419-9.082 10.311-15.955 17.839-20.7 7.528-4.746 15.874-7.037 25.039-7.037 7.037 0 12.846 1.145 17.347 3.518 4.582 2.373 8.182 5.236 10.883 8.51 2.7 3.272 4.746 6.382 6.137 9.327h1.554v-19.8h27.821v121.749c0 10.228-2.454 18.737-7.364 25.447-4.91 6.709-11.538 11.7-20.048 15.055-8.509 3.355-18.165 4.991-28.884 4.991Zm.245-71.266c5.974 0 11.047-1.473 15.302-4.337 4.173-2.945 7.446-7.118 9.573-12.519 2.21-5.482 3.274-12.027 3.274-19.637 0-7.609-1.064-14.155-3.274-19.8-2.127-5.646-5.318-10.064-9.491-13.255-4.174-3.11-9.329-4.746-15.384-4.746s-11.537 1.636-15.792 4.91c-4.173 3.272-7.365 7.772-9.492 13.418-2.128 5.727-3.191 12.191-3.191 19.392 0 7.2 1.063 13.745 3.273 19.228 2.127 5.482 5.318 9.736 9.573 12.764 4.174 3.027 9.41 4.582 15.629 4.582Zm141.56-26.51V71.839h28.23v119.786h-27.412v-21.273h-1.227c-2.7 6.709-7.119 12.191-13.338 16.446-6.137 4.255-13.747 6.382-22.748 6.382-7.855 0-14.81-1.718-20.783-5.237-5.974-3.518-10.72-8.591-14.075-15.382-3.355-6.709-5.073-14.891-5.073-24.464V71.839h28.312v71.921c0 7.609 2.046 13.664 6.219 18.083 4.173 4.5 9.655 6.709 16.365 6.709 4.173 0 8.183-.982 12.111-3.028 3.927-2.045 7.118-5.072 9.655-9.082 2.537-4.091 3.764-9.164 3.764-15.218Zm65.707-109.395v159.796h-28.23V31.828h28.23Zm44.841 162.169c-7.61 0-14.402-1.391-20.457-4.091-6.055-2.7-10.883-6.791-14.32-12.109-3.518-5.319-5.237-11.946-5.237-19.801 0-6.791 1.228-12.355 3.765-16.773 2.536-4.419 5.891-7.937 10.228-10.637 4.337-2.618 9.164-4.664 14.647-6.055 5.4-1.391 11.046-2.373 16.856-3.027 7.037-.737 12.683-1.391 17.102-1.964 4.337-.573 7.528-1.555 9.574-2.782 1.963-1.309 3.027-3.273 3.027-5.973v-.491c0-5.891-1.718-10.391-5.237-13.664-3.518-3.191-8.51-4.828-15.056-4.828-6.955 0-12.356 1.473-16.447 4.5-4.009 3.028-6.71 6.546-8.183 10.719l-26.348-3.764c2.046-7.282 5.483-13.336 10.31-18.328 4.746-4.909 10.638-8.59 17.511-11.045 6.955-2.455 14.565-3.682 22.912-3.682 5.809 0 11.537.654 17.265 2.045s10.965 3.6 15.711 6.71c4.746 3.109 8.51 7.282 11.455 12.6 2.864 5.318 4.337 11.946 4.337 19.883v80.184h-27.166v-16.446h-.9c-1.719 3.355-4.092 6.464-7.201 9.328-3.109 2.864-6.955 5.237-11.619 6.955-4.828 1.718-10.229 2.536-16.529 2.536Zm7.364-20.701c5.646 0 10.556-1.145 14.729-3.354 4.173-2.291 7.364-5.237 9.655-9.001 2.292-3.763 3.355-7.854 3.355-12.273v-14.155c-.9.737-2.373 1.391-4.5 2.046-2.128.654-4.419 1.145-7.037 1.636-2.619.491-5.155.9-7.692 1.227-2.537.328-4.746.655-6.628.901-4.173.572-8.019 1.472-11.292 2.781-3.355 1.31-5.973 3.11-7.855 5.401-1.964 2.291-2.864 5.318-2.864 8.918 0 5.237 1.882 9.164 5.728 11.782 3.682 2.782 8.51 4.091 14.401 4.091Zm64.643 18.328V71.839h27.412v19.965h1.227c2.21-6.955 5.974-12.274 11.292-16.038 5.319-3.763 11.456-5.645 18.329-5.645 1.555 0 3.355.082 5.237.163 1.964.164 3.601.328 4.91.573v25.938c-1.227-.41-3.109-.819-5.646-1.146a58.814 58.814 0 0 0-7.446-.49c-5.155 0-9.738 1.145-13.829 3.354-4.091 2.209-7.282 5.236-9.655 9.164-2.373 3.927-3.519 8.427-3.519 13.5v70.448h-28.312ZM222.077 39.192l-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
/>
</g>
<defs>
<radialGradient
id="c"
cx="0"
cy="0"
r="1"
gradientTransform="rotate(118.122 171.182 60.81) scale(205.794)"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#FF41F8" />
<stop offset=".707" stop-color="#FF41F8" stop-opacity=".5" />
<stop offset="1" stop-color="#FF41F8" stop-opacity="0" />
</radialGradient>
<linearGradient
id="b"
x1="0"
x2="982"
y1="192"
y2="192"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F0060B" />
<stop offset="0" stop-color="#F0070C" />
<stop offset=".526" stop-color="#CC26D5" />
<stop offset="1" stop-color="#7702FF" />
</linearGradient>
<clipPath id="a"><path fill="#fff" d="M0 0h982v239H0z" /></clipPath>
</defs>
</svg>
<h1>Hello, {{ title() }}</h1>
<p>Congratulations! Your app is running. 🎉</p>
</div>
<div class="divider" role="separator" aria-label="Divider"></div>
<div class="right-side">
<div class="pill-group">
@for (item of [
{ title: 'Explore the Docs', link: 'https://angular.dev' },
{ title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' },
{ title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'},
{ title: 'CLI Docs', link: 'https://angular.dev/tools/cli' },
{ title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' },
{ title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' },
]; track item.title) {
<a
class="pill"
[href]="item.link"
target="_blank"
rel="noopener"
>
<span>{{ item.title }}</span>
<svg
xmlns="http://www.w3.org/2000/svg"
height="14"
viewBox="0 -960 960 960"
width="14"
fill="currentColor"
>
<path
d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h280v80H200v560h560v-280h80v280q0 33-23.5 56.5T760-120H200Zm188-212-56-56 372-372H560v-80h280v280h-80v-144L388-332Z"
/>
</svg>
</a>
}
</div>
<div class="social-links">
<a
href="https://github.com/angular/angular"
aria-label="Github"
target="_blank"
rel="noopener"
>
<svg
width="25"
height="24"
viewBox="0 0 25 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
alt="Github"
>
<path
d="M12.3047 0C5.50634 0 0 5.50942 0 12.3047C0 17.7423 3.52529 22.3535 8.41332 23.9787C9.02856 24.0946 9.25414 23.7142 9.25414 23.3871C9.25414 23.0949 9.24389 22.3207 9.23876 21.2953C5.81601 22.0377 5.09414 19.6444 5.09414 19.6444C4.53427 18.2243 3.72524 17.8449 3.72524 17.8449C2.61064 17.082 3.81137 17.0973 3.81137 17.0973C5.04697 17.1835 5.69604 18.3647 5.69604 18.3647C6.79321 20.2463 8.57636 19.7029 9.27978 19.3881C9.39052 18.5924 9.70736 18.0499 10.0591 17.7423C7.32641 17.4347 4.45429 16.3765 4.45429 11.6618C4.45429 10.3185 4.9311 9.22133 5.72065 8.36C5.58222 8.04931 5.16694 6.79833 5.82831 5.10337C5.82831 5.10337 6.85883 4.77319 9.2121 6.36459C10.1965 6.09082 11.2424 5.95546 12.2883 5.94931C13.3342 5.95546 14.3801 6.09082 15.3644 6.36459C17.7023 4.77319 18.7328 5.10337 18.7328 5.10337C19.3942 6.79833 18.9789 8.04931 18.8559 8.36C19.6403 9.22133 20.1171 10.3185 20.1171 11.6618C20.1171 16.3888 17.2409 17.4296 14.5031 17.7321C14.9338 18.1012 15.3337 18.8559 15.3337 20.0084C15.3337 21.6552 15.3183 22.978 15.3183 23.3779C15.3183 23.7009 15.5336 24.0854 16.1642 23.9623C21.0871 22.3484 24.6094 17.7341 24.6094 12.3047C24.6094 5.50942 19.0999 0 12.3047 0Z"
/>
</svg>
</a>
<a
href="https://x.com/angular"
aria-label="X"
target="_blank"
rel="noopener"
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
alt="X"
>
<path
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
/>
</svg>
</a>
<a
href="https://www.youtube.com/channel/UCbn1OgGei-DV7aSRo_HaAiw"
aria-label="Youtube"
target="_blank"
rel="noopener"
>
<svg
width="29"
height="20"
viewBox="0 0 29 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
alt="Youtube"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M27.4896 1.52422C27.9301 1.96749 28.2463 2.51866 28.4068 3.12258C29.0004 5.35161 29.0004 10 29.0004 10C29.0004 10 29.0004 14.6484 28.4068 16.8774C28.2463 17.4813 27.9301 18.0325 27.4896 18.4758C27.0492 18.9191 26.5 19.2389 25.8972 19.4032C23.6778 20 14.8068 20 14.8068 20C14.8068 20 5.93586 20 3.71651 19.4032C3.11363 19.2389 2.56449 18.9191 2.12405 18.4758C1.68361 18.0325 1.36732 17.4813 1.20683 16.8774C0.613281 14.6484 0.613281 10 0.613281 10C0.613281 10 0.613281 5.35161 1.20683 3.12258C1.36732 2.51866 1.68361 1.96749 2.12405 1.52422C2.56449 1.08095 3.11363 0.76113 3.71651 0.596774C5.93586 0 14.8068 0 14.8068 0C14.8068 0 23.6778 0 25.8972 0.596774C26.5 0.76113 27.0492 1.08095 27.4896 1.52422ZM19.3229 10L11.9036 5.77905V14.221L19.3229 10Z"
/>
</svg>
</a>
</div>
</div>
</div>
</main>
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * The content above * * * * * * * * * * * * -->
<!-- * * * * * * * * * * is only a placeholder * * * * * * * * * * * -->
<!-- * * * * * * * * * * and can be replaced. * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<!-- * * * * * * * * * * End of Placeholder * * * * * * * * * * * * -->
<!-- * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -->
<router-outlet />

View File

@@ -0,0 +1,3 @@
import { Routes } from '@angular/router';
export const routes: Routes = [];

View File

View File

@@ -0,0 +1,23 @@
import { TestBed } from '@angular/core/testing';
import { App } from './app';
describe('App', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [App],
}).compileComponents();
});
it('should create the app', () => {
const fixture = TestBed.createComponent(App);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});
it('should render title', async () => {
const fixture = TestBed.createComponent(App);
await fixture.whenStable();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, web');
});
});

12
apps/web/src/app/app.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Component, signal } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
imports: [RouterOutlet],
templateUrl: './app.html',
styleUrl: './app.scss'
})
export class App {
protected readonly title = signal('web');
}

13
apps/web/src/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Web</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

6
apps/web/src/main.ts Normal file
View File

@@ -0,0 +1,6 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { App } from './app/app';
bootstrapApplication(App, appConfig)
.catch((err) => console.error(err));

1
apps/web/src/styles.scss Normal file
View File

@@ -0,0 +1 @@
/* You can add global styles to this file, and also import other style files */

View File

@@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"include": [
"src/**/*.ts"
],
"exclude": [
"src/**/*.spec.ts"
]
}

33
apps/web/tsconfig.json Normal file
View File

@@ -0,0 +1,33 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"compileOnSave": false,
"compilerOptions": {
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"skipLibCheck": true,
"isolatedModules": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "ES2022",
"module": "preserve"
},
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
},
"files": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View File

@@ -0,0 +1,15 @@
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"vitest/globals"
]
},
"include": [
"src/**/*.d.ts",
"src/**/*.spec.ts"
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,479 @@
# Ashen Realms Vertical Slice
## World & Content Design V1
Spielwelt, Gebiete, Orte, Reisen, Jagd, Gegner, Progression und gebietsspezifischer Loot
> Zweck dieser Doku Diese Doku beschreibt den konkreten World- und Content-Scope des ersten spielbaren Vertical Slice. Sie ergänzt das Design Manifest V1: Das Manifest erklärt die Grundsätze; dieses Dokument legt fest, wie diese Grundsätze in der ersten Spielwelt umgesetzt werden.
**Status:** Arbeitsgrundlage V1
**Scope:** Start-Hub + 3 vollständige Progressionsgebiete
**Zielbereich:** ca. Level 17 im ersten ausgearbeiteten Slice
**Nicht enthalten:** vollständige Lore, finales Balancing, technische API-Spezifikation, PvP, Gilden, Crafting und Economy-Systeme
## 1. Vertical-Slice-Überblick
Der Vertical Slice soll den vollständigen Kernloop von Ashen Realms in einer kleinen Welt beweisen: Der Spieler beginnt in einem sicheren Hub, reist in gefährlichere Gebiete, sucht aktiv nach Gegnern, gewinnt Loot, verbessert seinen Charakter und überwindet dadurch natürliche Progressionsgrenzen.
> Kernloop Reisen → Jagd / Suchen → Gegner wählen → rundenbasiert kämpfen → Loot & XP → Ausrüstung verbessern → Elite/Boss überwinden → neuen Bereich entdecken
| Bereich | Level | Item-Tier | Schwerpunkt | Abschluss |
| --- | --- | --- | --- | --- |
| Graufurt | | | Sicherer Hub, Versorgung, Einstieg | Südtor / Aschenfelder |
| Aschenfelder | 13 | Tier 1 | Einführung: Jagd, Reise, Loot, Telegraphing | Hauptmann der Aschenbande |
| Dämmerwald | 35 | Tier 2 | Status-Effekte, Gebietswährung, erstes Set | Schattenalpha |
| Vergessene Ruinen | 57 | Tier 3 | Unterbrechen, defensive Gegner, 2-Phasen-Boss | Knochenfürst |
### 1.1 Weltstruktur
Die Welt ist nicht frei begehbar. Sie besteht aus illustrierten Orten, die über Reiseverbindungen miteinander verknüpft sind. Der Spieler wählt sein Ziel, startet eine Reise und kann unterwegs zufälligen Begegnungen ausgesetzt sein. Gebiete besitzen keine automatische Gegnerskalierung.
- Der Spieler darf grundsätzlich auch in zu starke Gebiete reisen.
- Gebiete werden durch echte Gefahr statt durch harte Level-Sperren begrenzt.
- Frühere Gebiete bleiben schwächer; Rückkehr soll den Machtzuwachs sichtbar machen.
- Neue Gebiete führen neue visuelle oder spielerische Ideen ein, nicht nur höhere Zahlen.
### 1.2 Content-Knoten
Jeder Ort besitzt eine klare Funktion. Nicht jeder Ort braucht alle Systeme. Ein Ort kann primär Jagdzone, Quest-Ort, Versorgungsort, Elite-Ort, Boss-Ort oder Übergang sein. Übergangsorte dürfen bewusst wenig Content haben, solange sie Atmosphäre und Weltstruktur tragen.
## 2. Gebietsübergreifende Regeln
### 2.1 Reisen und Reisegefahr
Reisen benötigt kurze reale Zeit. Während der Reise kann ein Hinterhalt ausgelöst werden. Der Timer soll Reisegefühl erzeugen und schnelles Durchklicken verhindern, ohne den Spieler mit langen Wartezeiten zu bestrafen.
| Reisetyp | Richtwert | Gefahr |
| --- | --- | --- |
| Sehr kurze Verbindung | ca. 10 Sek. | sehr niedrig |
| Normale Verbindung | ca. 1520 Sek. | niedrig bis mittel |
| Gefährlicher Weg | ca. 2030 Sek. | mittel bis hoch |
> Designregel Die Reisezeit ist kein Selbstzweck. Sie ist eine kurze Spannungsphase, in der ein Hinterhalt passieren kann und in der die Welt Distanz bekommt.
### 2.2 Jagd / Begegnungssuche
Normale Gegner werden aktiv gesucht. Die Bezeichnung darf zur Szene passen: Im Wald „Jagd“, in einer Krypta „durchsuchen“. Mechanisch bleibt es dasselbe Encounter-System.
1. Spieler startet Jagd / Suche.
1. Nach kurzer Suche werden 23 mögliche Begegnungen angeboten.
1. Gegner werden mit Name, Level und relativer Gefahreneinschätzung angezeigt.
1. Der Spieler entscheidet, welchen Gegner er angreift oder ob er weitersucht.
- Mögliche Einschätzungen: Schwach, Passend, Stark, Sehr gefährlich, Tödlich.
- Das Spiel warnt, verbietet einen Angriff aber nicht.
### 2.3 Gegner- und Bosslogik
- Normale Gegner: eine klar erkennbare Besonderheit reicht meist aus.
- Elites: kombinieren 23 bekannte Mechaniken und dienen als Gear-Prüfung.
- Bosse: Abschlussprüfung eines Gebiets; relevante Belohnung ist garantiert.
- Gefährliche Aktionen werden möglichst angekündigt und sollen Gegenentscheidungen erlauben.
- Normale Kämpfe im passenden Gebiet sollen überwiegend zuverlässig gewonnen werden.
### 2.4 Loot und gebietsspezifische Progression
Equipment ist die primäre Quelle von Kampfstärke. Jedes Gebiet führt eine neue Item-Stufe ein. Der Spieler erreicht das Gebiet mit Gear aus dem vorherigen Bereich und ersetzt seine Ausrüstung Schritt für Schritt.
- Gezielter Loot: bestimmte Gegner besitzen erkennbare Wunschitems.
- Elites und Bosse sind wichtige Quellen für seltene oder besondere Items.
- Gebietswährungen federn Pech ab und bieten langfristig garantierten Fortschritt.
- Händler schließen Lücken; die besten Belohnungen bleiben an Herausforderungen gebunden.
## 3. Start-Hub Graufurt
> Rolle Graufurt ist der sichere Ausgangspunkt des Vertical Slice. Hier organisiert sich der Spieler, füllt Verbrauchsgegenstände auf, erhält erste Aufgaben und betritt von dort die gefährliche Welt.
- Kein zufälliger Kampf innerhalb der Stadt.
- Händler und grundlegende Versorgung.
- Ausrüstung und Kampftasche verwalten.
- Questgeber und später soziale Funktionen.
- Direkte Verbindung zum Südtor und damit in die Aschenfelder.
Graufurt soll im Vertical Slice nicht mit Systemen überladen werden. Bank, Auktionshaus, Gildenfunktionen, Crafting oder komplexe Dienstleistungen sind für V1 nicht notwendig.
## 4. Gebiet 1 Aschenfelder
| Merkmal | Festlegung |
| --- | --- |
| Empfohlener Bereich | Level 13 |
| Item-Stufe | Tier 1 |
| Gebietsrolle | Einführung in Reise, Jagd, Kampf, Loot und erste Eliteprüfung |
| Reisegefahr | niedrig |
| Abschluss | Hauptmann der Aschenbande / Aschengrube |
Die Aschenfelder sind trocken, verbrannt und vom Verfall einer alten Handelsroute geprägt. Der Ort soll früh ein kleines Mysterium erzeugen: Warum ist die Region zerstört und warum nehmen Überfälle und aggressive Tiere zu? Die Geschichte bleibt zunächst bewusst knapp und wird über Spuren und Orte statt lange Textblöcke vermittelt.
### 4.1 Reise- und Ortsstruktur
| Ort | Funktion | Verbindungen |
| --- | --- | --- |
| Südtor von Graufurt | Übergang / Orientierung | Graufurt ↔ Verbrannte Straße |
| Verbrannte Straße | erste echte Jagdzone | Südtor ↔ Wachtposten |
| Verlassener Wachtposten | Quest-/Story-Knoten, stärkere Gegner | Straße ↔ Aschengrube |
| Aschengrube | gefährlichster Ort, Elite-/Mini-Boss-Abschluss | Wachtposten → nächstes Gebiet |
### 4.2 Südtor von Graufurt
Das Südtor ist ein atmosphärischer Übergang zwischen sicherem Hub und Wildnis. Es soll nur kurz besucht werden und keine eigene Content-Schleife erzeugen.
- Aktionen: nach Graufurt zurückkehren, zur Verbrannten Straße reisen, Wachposten ansprechen, Gebietsinformationen ansehen.
- Noch keine reguläre Jagd oder nur sehr schwache Begegnungen.
- Reise zur Verbrannten Straße: ca. 10 Sekunden, etwa 5 % Hinterhaltchance.
- Mögliche Reisebegegnung: schwacher Plünderer oder verwildertes Tier.
### 4.3 Verbrannte Straße
> Gameplay-Ziel Hier erlebt der Spieler erstmals den vollständigen Mini-Loop: Jagd starten → Gegner wählen → kämpfen → Loot erhalten → Item vergleichen und ausrüsten.
| Gegner | Level | Mechanik | Loot-Rolle |
| --- | --- | --- | --- |
| Aschenratte | 1 | Basisangriff, keine Spezialmechanik | Einführung, kleine Ressourcen, niedrige Gear-Chance |
| Verwilderter Straßenhund | 12 | gelegentliche Blutung | Fell, Heiltrank-Chance, frühe Handschuhe/Stiefel |
| Straßenräuber | 2 | angekündigter schwerer Schlag | Silber, Waffe/Rüstung, erste bessere Räuberausrüstung |
| Verkohlter Plünderer | 3 selten | deutlich stärker; frühes Langzeitziel | seltener, klar benannter Drop |
- Jagd liefert 23 Begegnungen zur Auswahl.
- Spuren untersuchen weist auf den verlassenen Wachtposten hin.
- Innerhalb der ersten 35 Kämpfe soll sehr wahrscheinlich das erste spürbare Equipment-Upgrade fallen.
- Reise Wachtposten: ca. 15 Sekunden, etwa 10 % Hinterhaltchance.
### 4.4 Verlassener Wachtposten
Der Wachtposten verbindet erstmals Quest, Story-Hinweis, stärkere Jagd und gezieltes Elite-Farming. Ein zurückgebliebener Wachmann dient als erster einfacher NPC-Knoten.
- Aktionen: NPC sprechen, Jagd beginnen, Wachtposten untersuchen, Vorräte durchsuchen, zur Straße oder Aschengrube reisen.
- Story-Aufgabe: herausfinden, wer die Überfälle organisiert; Spuren führen zur Aschengrube.
| Gegner | Rolle |
| --- | --- |
| Straßenräuber | bekannter Gegner, nun häufiger |
| Aschenhund | aggressiver Bestiengegner |
| Plünderer-Späher | leichter humanoider Gegner |
| Plünderer-Veteran | stärker; schwerer Angriff und defensive Haltung |
| Plündererhauptmann | erste Elite; klares Wunschitem |
### 4.5 Aschengrube
Die Aschengrube ist die erste klare Gebietsspitze. Hier prüft das Spiel, ob der Spieler die ersten Upgrades verstanden und genutzt hat.
| Gegner | Mechanik |
| --- | --- |
| Aschenwühler | zäher Nahkämpfer |
| Plünderer-Veteran | schwerer Telegraph + Defensive |
| Verbrannter Jagdhund | Blutung, aggressiver bei wenig Leben |
> **Mini-Boss:** Hauptmann der Aschenbande Level 3. Schwerer angekündigter Angriff, defensive Phase / Rüstungserhöhung und Aggressivität unter 30 % Leben. Der Sieg garantiert relevante Tier-1-Beute und öffnet den Weg Richtung Dämmerwald.
- Garantierter relevanter Drop; zusätzliche Chance auf Set-/Spezialitem.
- Mögliches Wunschitem: Aschenklinge deutlich stärker als Startwaffen, optional Bonus gegen Humanoide.
- Gebietsabschluss geschieht durch Weltfortschritt, nicht durch eine reine Levelmeldung.
## 5. Gebiet 2 Dämmerwald
| Merkmal | Festlegung |
| --- | --- |
| Empfohlener Bereich | Level 35 |
| Item-Stufe | Tier 2 |
| Gebietsrolle | erste echte Progressionswand |
| Neue Mechaniken | Blutung, Gift, starke Telegraphen, Gebietswährung, erstes Set |
| Abschluss | Schattenalpha |
Der Dämmerwald ist ein dichter, alter Wald mit dunklem Kronendach, Nebel, feuchtem Boden und überwucherten Wegen. Er soll sich deutlich von den offenen Aschenfeldern absetzen. Spieler mit Tier-1-Gear können hier beginnen, merken aber sofort, dass ihre alte Ausrüstung zunehmend ersetzt werden muss.
### 5.1 Ortsstruktur
| Ort | Funktion |
| --- | --- |
| Waldsaum | Einstieg, erste Tier-2-Jagd |
| Nebelpfad | Reisegefahr, Gift und Telegraphing |
| Verfallener Jägerschrein | semi-sicherer Hub, Quests, Händler, Gebietswährung |
| Dornenlichtung | stärkste reguläre Jagdzone, Elite und Set-Farming |
| Höhle des Schattenalphas | Gebietsboss und Abschluss |
Grundverlauf: Aschengrube → Waldsaum → Nebelpfad → Jägerschrein → Dornenlichtung → Schattenalphahöhle. Zwischen Nebelpfad und Dornenlichtung kann eine Abkürzung / alternative Verbindung bestehen, um die Welt nicht vollständig linear wirken zu lassen.
### 5.2 Waldsaum
| Gegner | Level | Mechanik |
| --- | --- | --- |
| Dämmerwolf | 3 | Biss + Blutung |
| Moorkrabbler | 3 | geringer Angriff, relativ hohe Rüstung |
| Waldplünderer | 4 | angekündigter schwerer Hieb |
| Schwarzmähnenwolf | 5 selten | stärkere Blutung; seltener Drop |
- Vom vorherigen Gebiet ca. 15 Sekunden Reise, ca. 10 % Hinterhaltchance.
- Erste Tier-2-Items ersetzen sichtbar Tier-1-Gear.
### 5.3 Nebelpfad
- Deutlich höhere Reisegefahr: etwa 1520 % Hinterhaltchance.
| Gegner | Mechanik |
| --- | --- |
| Giftspinne | Gift über mehrere Runden; niedrige HP |
| Dämmerwolf | Blutung |
| Verdorbener Hirsch | angekündigter Ansturm |
| Schwarzmähnenwolf | seltene Reise-/Jagdbegegnung |
- Spuren können Jägerschrein und Dornenlichtung als interessante Ziele hervorheben.
### 5.4 Verfallener Jägerschrein
Der Schrein ist ein semi-sicherer Gebiets-Hub. Innerhalb des Schreins finden keine Kämpfe statt; das umliegende Gebiet bleibt gefährlich.
- NPC: Elyra, die verbannte Jägerin erklärt die aggressiven Tiere, gibt Jagd-/Eliteaufträge und weist auf den Schattenalpha hin.
- Gebietswährung: Dämmermarken.
- Quellen: Jagdquests, Elitegegner, seltene Begegnungen, Boss, optional geringe Chance bei normalen Gegnern.
- Händler: Heiltränke, optional Gegengift, schwache Tier-2-Basisausrüstung sowie gezielte Lückenfüller gegen Dämmermarken.
| Beispielangebot | Kosten |
| --- | --- |
| Tier-2-Stiefel | 15 Dämmermarken |
| Tier-2-Handschuhe | 20 Dämmermarken |
| bestimmtes Set-Teil | 30 Dämmermarken |
### 5.5 Dornenlichtung
| Gegner | Level | Mechanik |
| --- | --- | --- |
| Dornenbestie | 45 | hohe Rüstung, Rüstungsbruch |
| Schwarzmähnenwolf | 5 | Blutung + Enrage unter 30 % HP |
| Giftweberin | 5 | Gift + vorbereiteter stärkerer Giftangriff |
> **Elite:** Der Graufang Level 5. Blutung, steigende Angriffskraft unter 50 % HP und angekündigter Rasender Biss unter 25 % HP. Er soll beim ersten Besuch eventuell noch zu stark sein und später als sichtbares Fortschrittsziel zurückkehren.
- Set-Fokus: Set des Dämmerjägers Helm, Handschuhe, Stiefel.
- 2 Teile: kleiner Angriffsbonus. 3 Teile: Bonus gegen Bestien.
- Mindestens ein Set-Teil sollte zusätzlich über Dämmermarken erreichbar sein, damit Pech nicht blockiert.
### 5.6 Höhle des Schattenalphas
> **Boss:** Schattenalpha Level 5. Basisbiss, Blutung, angekündigter Sprungangriff, Verstärkung / Jagdruf und Raserei im niedrigen HP-Bereich. Der Boss soll mit Tier-1-Gear praktisch nicht sinnvoll, mit ersten Tier-2-Upgrades knapp und mit gutem Tier-2-Gear zuverlässig schaffbar sein.
- Garantiert: relevantes Tier-2-Item, großes XP-Paket und Dämmermarken.
- Seltene Belohnung: Fang des Schattenalphas starke Tier-2-Waffe, optional Bonus gegen blutende Gegner.
- Nach dem Sieg wird ein überwucherter Steinweg zu den Vergessenen Ruinen entdeckt.
## 6. Gebiet 3 Vergessene Ruinen
| Merkmal | Festlegung |
| --- | --- |
| Empfohlener Bereich | Level 57 |
| Item-Stufe | Tier 3 |
| Gebietsrolle | stärkerer taktischer Kontrast zu Bestiengebieten |
| Neue Mechaniken | Unterbrechen, defensive Phasen, Parieren, erste Spielstil-Items, Bossphasen |
| Abschluss | Knochenfürst |
Nach dem Wald wechselt das Spiel bewusst zu einer älteren, unheimlicheren Ruinen- und Kryptenlandschaft. Untote, Grabräuber und Kultisten ersetzen die Bestien. Das Gebiet soll zeigen, dass stärkere Gegner nicht nur mehr HP besitzen, sondern auf bekannte Spieleraktionen reagieren.
### 6.1 Ortsstruktur
| Ort | Funktion |
| --- | --- |
| Zerbrochenes Tor | Einstieg, erste Tier-3-Jagd |
| Hof der Gefallenen | Unterbrechen wird relevant |
| Kapelle der letzten Wacht | sicherer Versorgungsknoten / Händler |
| Krypten von Velkar | Elitezone und Set-Farming |
| Halle des Knochenfürsten | zweiphasiger Gebietsboss |
Grundverlauf: Dämmerwald → Zerbrochenes Tor → Hof der Gefallenen → Kapelle → Krypten → Halle des Knochenfürsten. Hof, Kapelle und Krypten dürfen als kleine Dreiecksstruktur verbunden werden, um mehr Reiseoptionen zu bieten.
### 6.2 Zerbrochenes Tor
- Reise vom Dämmerwald: ca. 20 Sekunden, etwa 15 % Hinterhaltchance.
| Gegner | Level | Mechanik |
| --- | --- | --- |
| Verlorener Soldat | 5 | Basisangriff + langsamer stärkerer Angriff |
| Grabräuber | 5 | kann sich geringfügig heilen |
| Ruinenwächter | 6 | hohe Rüstung, geringer Schaden |
| Namenloser Bannerträger | 7 selten | hohe Rüstung, schwerer Angriff, Enrage |
- Besonderer möglicher Drop: Bannerfragment der letzten Wacht.
### 6.3 Hof der Gefallenen
| Gegner | Mechanik |
| --- | --- |
| Knochenrufer | Ruf der Toten wird angekündigt und kann unterbrochen werden; sonst Heilung / Verstärkung |
| Gefallener Ritter | hohe Rüstung; wechselt in Verteidigungsstellung |
| Grabkriecher | niedrige HP, aggressiver Schadensdruck |
| Aschenpriester | selten; gefährlicher vorbereiteter Fluch, unterbrechbar |
> Neue Lernkurve Unterbrechen wird hier nicht nur optional nützlich, sondern sichtbar wertvoll. Der Spieler soll erkennen: Bestimmte Gegner werden nicht effizienter besiegt, indem man jede Runde nur Angriff wählt.
### 6.4 Kapelle der letzten Wacht
- Sicherer Versorgungsknoten innerhalb des Gebiets.
- NPC: Bruder Caelan, letzter Hüter erklärt das Erwachen der Toten, gibt Siegel-/Kryptenaufgaben und Hinweise auf den Knochenfürsten.
- Gebietswährung: Siegelbruchstücke.
- Quellen: Untote, Elites, Quests, Kryptenbegegnungen und Boss.
- Händler: Tier-3-Lückenfüller, Heiltränke, optional ein situativer Verbrauchsgegenstand; nichts darf Pflicht für den Boss sein.
| Beispielangebot | Kosten |
| --- | --- |
| Tier-3-Handschuhe | 20 Siegelbruchstücke |
| Tier-3-Stiefel | 25 Siegelbruchstücke |
| Set-Teil | 35 Siegelbruchstücke |
### 6.5 Krypten von Velkar
Die Krypten sind die härteste reguläre Zone des Gebiets. Die UI darf statt „Jagd“ passend zur Szene „Krypta durchsuchen“ sagen, technisch bleibt es dasselbe Encounter-System.
| Gegner | Mechanik |
| --- | --- |
| Grabwächter | sehr hohe Rüstung, langsamer Telegraph |
| Verdorbener Akolyth | Magieangriff, vorbereiteter unterbrechbarer Fluch |
| Knochenritter | Parierhaltung: Angriff während der Haltung verursacht reduzierten Schaden und Gegenangriff |
> **Elite:** Sir Varos, der Gebrochene Level 7. Schildwall erhöht Rüstung für 2 Runden, „Hinrichtung“ ist ein stark angekündigter unterbrechbarer Angriff, „Letzter Schwur“ erhöht unter 30 % HP den Schaden. Er kombiniert bekannte Mechaniken statt neue Systeme einzuführen.
- Set der letzten Wacht: Helm, Brust, Amulett.
- 2 Teile: Leben / Rüstung. 3 Teile: Nach Verteidigen erhält der nächste Angriff einen kleinen Schadensbonus.
- Gebiet 3 führt damit erstmals Items ein, die Spielweise beeinflussen statt nur Werte zu erhöhen.
### 6.6 Halle des Knochenfürsten
> **Boss:** Der Knochenfürst Level 7. Zwei einfache Phasen. Phase 1: gepanzerter Herrscher mit Schildhaltung und angekündigtem Todesurteil. Phase 2 ab ca. 40 % HP: weniger Defensive, höherer Druck und unterbrechbarer Ruf der Krypta.
- Phase 1 belohnt Geduld, Verteidigung und Timing.
- Phase 2 wird zum Druck-/Endspurt.
- Garantiert: großes XP-Paket, Siegelbruchstücke und hochwertiges Tier-3-Item.
- Seltene Belohnung: Klinge des Gebrochenen Königs Bonus auf den nächsten Angriff nach erfolgreichem Unterbrechen.
- Hinter der Halle führt ein alter Versorgungstunnel perspektivisch zur Schwarzsteinmine / Gebiet 4. Dieses Gebiet ist in der aktuellen Doku noch nicht ausgearbeitet.
## 7. Progressions- und Lernkurve
Der Vertical Slice soll neue Mechaniken schrittweise aufbauen. Jedes Gebiet verwendet bekannte Grundlagen weiter und ergänzt nur wenige neue Anforderungen.
| Abschnitt | Was der Spieler lernt | Was bewusst noch nicht kommt |
| --- | --- | --- |
| Aschenfelder | Jagd, Reise, Gegnerwahl, erstes Loot-Upgrade, Telegraphing, erste Elite | Gebietswährung, komplexe Sets, harte Status-Kombinationen |
| Dämmerwald | Blutung/Gift, höheres Reiserisiko, Gebietswährung, erstes Set, echter Boss | Mana, Talente, Elementarresistenzen, Crafting |
| Vergessene Ruinen | Unterbrechen, defensive Phasen, Parieren, spielstilprägende Items, Bossphasen | komplexe Schadensarten, Klassenbaum, Gruppenmechaniken |
### 7.1 Erwartete Gear-Progression
- Tier 1: Startausrüstung wird ersetzt; einzelne Upgrades sollen sehr schnell spürbar sein.
- Tier 2: Gebiet wird mit Tier-1-Gear begonnen; Tier-2-Gear macht normale Kämpfe zunehmend zuverlässig und den Boss realistisch.
- Tier 3: bekannte Werte bleiben verständlich, aber einzelne Items beginnen, bestimmte Kampfentscheidungen zu belohnen.
> Balance-Ziel Der Spieler soll den Unterschied zwischen „zu früh“, „knapp machbar“ und „jetzt bin ich bereit“ primär über seine Ausrüstung erleben.
## 8. Content-Inventar des aktuellen Slice
| Kategorie | Aktueller Stand |
| --- | --- |
| Sichere Haupt-Hubs | 1: Graufurt |
| Ausgearbeitete Progressionsgebiete | 3: Aschenfelder, Dämmerwald, Vergessene Ruinen |
| Ausgearbeitete Orte | 14 inkl. Südtor und Gebiets-Hubs/Bossorte |
| Normale / seltene Gegnerkonzepte | ca. 20, inkl. wiederverwendeter Varianten |
| Elites / Mini-Bosse | Plündererhauptmann, Hauptmann der Aschenbande, Graufang, Sir Varos |
| Gebietsbosse | Schattenalpha, Knochenfürst; Aschengrube als Mini-Boss-Abschluss |
| Gebietswährungen | Dämmermarken, Siegelbruchstücke |
| Sets | Set des Dämmerjägers, Set der letzten Wacht |
| Item-Tiers | Tier 13 |
## 9. Noch offene Entscheidungen
Die folgenden Punkte wurden bewusst noch nicht finalisiert. Sie sollten erst in den nächsten Designschritten konkretisiert werden:
- Exakte Kampfwerte: HP, Angriff, Rüstung, Schadensformel und Verteidigen-Wirkung.
- Exakte XP-Kurve und Leveldauer.
- Konkrete Itemlisten je Tier und Slot, inklusive Drop-Raten.
- Exakte Kosten und Ausbeute der Gebietswährungen.
- Fluchtchance und Verhalten nach erfolgreicher / fehlgeschlagener Flucht.
- Konkrete Anzahl und Inhalt der Quests je Ort.
- Finale Namen, Lore und visuelle Art Direction einzelner NPCs und Gegner.
- Ob Aschenfelder bereits eine eigene Gebietswährung erhalten oder erst Dämmerwald das System einführt.
- Ob Gebiet 4 (Schwarzsteinmine) noch Teil desselben Vertical Slice wird oder erst in einer späteren Content-Erweiterung folgt.
## 10. Datengetriebene Content-Sicht
Diese Doku ist bewusst so strukturiert, dass die Inhalte später datengetrieben abgebildet werden können. Für die technische Umsetzung sollten Gebiete und Orte nicht als individuelle Spezialfälle programmiert werden.
| Content-Typ | Mindestens benötigte Felder |
| --- | --- |
| Gebiet | ID, Name, empfohlener Levelbereich, Item-Tier, Danger-Level, Artwork, Währung |
| Ort | ID, Gebiet, Name, Typ/Funktion, Artwork, verfügbare Aktionen, Reiseverbindungen |
| Reiseverbindung | Von, Nach, Reisezeit, Hinterhaltchance, Encounter-Pool |
| Gegner | ID, Name, Level, Werte, Mechanik-Bausteine, Loot-Tabelle, Encounter-Gewichtung |
| Elite/Boss | Basisgegnerdaten + Phasen/Trigger + garantierte Belohnungen |
| Encounter-Pool | Ort, mögliche Gegner, Gewichtung, seltene Begegnungen, Bedingungen |
| Shop | Ort/NPC, Währung, Angebote, Kosten, Freischaltbedingungen |
| Loot | Item, Quelle, Dropchance/garantiert, Menge, Bedingungen |
> Architekturregel Spezielle Präsentation darf variieren („Jagd“, „Krypta durchsuchen“), aber die zugrunde liegenden Systeme sollen möglichst dieselben Datenmodelle und wiederverwendbaren Mechanik-Bausteine verwenden.
## 11. Empfohlener nächster Designschritt
Die Weltstruktur ist nun konkret genug, um das numerische Spielsystem darauf auszurichten. Der nächste sinnvolle Schritt ist nicht weitere Lore, sondern ein kleines Balancing-Modell für Kampf, Gegner-Power und Item-Tiers.
**1. **Basiswerte und Schadensformel für Level 1 festlegen.
**2. **Zielwerte für normale Gegner, Elites und Bosse je Gebiet definieren.
**3. **Tier-1- bis Tier-3-Itembudgets pro Slot festlegen.
**4. **Anschließend konkrete Itemlisten und Loot-Tabellen erstellen.
**5. **Erst danach Quests und Story-Text detailliert ausformulieren.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

16389
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

30
package.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "ashen-realms",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"doc": "docs"
},
"scripts": {
"dev:web": "npm run start --workspace=@ashen-realms/web",
"dev:api": "npm run start:dev --workspace=@ashen-realms/api",
"build:web": "npm run build --workspace=@ashen-realms/web",
"build:api": "npm run build --workspace=@ashen-realms/api",
"build": "npm run build:web && npm run build:api",
"test": "npm run test --workspaces --if-present"
},
"workspaces": [
"apps/*",
"packages/*"
],
"repository": {
"type": "git",
"url": "https://gitea.forgecore.work/bastian/ashen-realms.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"private": "true"
}

View File

@@ -0,0 +1,7 @@
{
"name": "@ashen-realms/game-content",
"version": "0.0.1",
"private": true,
"main": "src/index.ts",
"types": "src/index.ts"
}

View File

View File

@@ -0,0 +1,7 @@
{
"name": "@ashen-realms/shared",
"version": "0.0.1",
"private": true,
"main": "src/index.ts",
"types": "src/index.ts"
}

View File