feat: Sharp style, Tailwind+shadcn, dark navbar, nav restructure, Go/monorepo guidelines
Build and Deploy Documentation / build (push) Failing after 9s
Build and Deploy Documentation / build (push) Failing after 9s
Styling: - Tailwind CSS v4 (preflight:false) integrated via PostCSS plugin - shadcn/ui New York style with --radius:0 (sharp corners everywhere) - Zero border-radius on ALL elements: buttons, code blocks, cards, admonitions, badges - Navbar FORCE dark (#0a0a0a) in both light/dark modes via !important - ThemeSynchronizer: bridges Docusaurus data-theme → shadcn .dark class - Active sidebar item gets left border accent instead of rounded bg React UI components (src/components/ui/index.tsx): - Badge (6 variants: wip, stable, planned, success, warning, outline) - Card + CardHeader + CardTitle + CardContent - Callout (note/info/success/warning/danger) - sharp, left-border style - PropertiesTable - monospace typed API reference tables - StatusIndicator - operational/degraded/outage/maintenance Navigation restructure: - Introduction → Platform → Products → Engineering → Runbooks - Old 'applications/' removed, moved to products/stock-market-pro/ - New Platform section: infrastructure overview + CI/CD pipeline docs - New Engineering section: monorepo structure, Go stack, conventions - New Runbooks section: incident severity, common procedures Implementation guidelines (engineering/guidelines.md): - Monorepo layout with go.work workspace - Go as primary language (why + standard library table) - Python as sidecar for data/ML workloads - Code conventions: gofmt, error wrapping, logging, testing - Git conventions: branch naming, conventional commits - Makefile targets per app - How to add a new application (step-by-step)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{"$schema":"https://ui.shadcn.com/schema.json","style":"new-york","rsc":false,"tsx":true,"tailwind":{"config":"tailwind.config.js","css":"src/css/custom.css","baseColor":"zinc","cssVariables":true,"prefix":""},"aliases":{"components":"@site/src/components","utils":"@site/src/lib/utils"},"iconLibrary":"none"}
|
||||
@@ -0,0 +1,159 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Engineering Guidelines
|
||||
|
||||
This document defines how applications in SKIC Playground are structured, built, and maintained.
|
||||
|
||||
## Monorepo Structure
|
||||
|
||||
All applications live in a **monorepo** at `skic-v1-playground` in Gitea. Each application is a top-level directory with its own build system.
|
||||
|
||||
```
|
||||
skic-v1-playground/
|
||||
│
|
||||
├── documentation/ # This Docusaurus site
|
||||
│
|
||||
├── stock-market-pro/ # Go application
|
||||
│ ├── cmd/
|
||||
│ │ └── server/
|
||||
│ │ └── main.go # Entrypoint
|
||||
│ ├── internal/
|
||||
│ │ ├── ingestor/ # Data ingestion
|
||||
│ │ ├── analysis/ # Technical analysis engine
|
||||
│ │ ├── signals/ # Signal generation
|
||||
│ │ └── notifier/ # Discord delivery
|
||||
│ ├── pkg/ # Shared public packages
|
||||
│ ├── Dockerfile
|
||||
│ ├── Makefile
|
||||
│ └── go.mod
|
||||
│
|
||||
├── shared/ # Cross-app shared libraries (Go modules)
|
||||
│ ├── discord/ # Discord client
|
||||
│ ├── config/ # Config loader
|
||||
│ └── telemetry/ # Logging, metrics, tracing
|
||||
│
|
||||
├── infra/ # Infrastructure-as-code
|
||||
│ ├── docker-compose.yml # Local dev stack
|
||||
│ └── gitea-runners/ # CI runner config
|
||||
│
|
||||
└── Makefile # Root-level make targets (build all, test all)
|
||||
```
|
||||
|
||||
## Go Workspace (go.work)
|
||||
|
||||
The monorepo uses [Go workspaces](https://go.dev/ref/mod#workspaces) so all Go modules can reference each other without publishing:
|
||||
|
||||
```
|
||||
# go.work
|
||||
go 1.22
|
||||
|
||||
use (
|
||||
./stock-market-pro
|
||||
./shared/discord
|
||||
./shared/config
|
||||
./shared/telemetry
|
||||
)
|
||||
```
|
||||
|
||||
This means local changes to `shared/` are immediately reflected across all Go apps without `replace` directives in individual `go.mod` files.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
### Applications — Go
|
||||
|
||||
Go is the primary language for all backend services and autonomous applications.
|
||||
|
||||
**Why Go:**
|
||||
- Statically typed, compiled, single binary deployment
|
||||
- Excellent concurrency primitives (goroutines, channels) — ideal for market data streams
|
||||
- Native cross-compilation
|
||||
- Fast build times, clean toolchain
|
||||
- Standard library covers most needs (HTTP, JSON, time, crypto)
|
||||
|
||||
**Standard libraries:**
|
||||
|
||||
| Package | Purpose |
|
||||
|---|---|
|
||||
| `net/http` | HTTP server / client |
|
||||
| `encoding/json` | JSON serialization |
|
||||
| `database/sql` + `modernc.org/sqlite` | SQLite (dev) |
|
||||
| `github.com/lib/pq` | PostgreSQL / TimescaleDB (prod) |
|
||||
| `github.com/rs/zerolog` | Structured logging |
|
||||
| `github.com/spf13/viper` | Config management |
|
||||
| `github.com/robfig/cron/v3` | Scheduled jobs |
|
||||
| `golang.org/x/sync` | Concurrency utilities |
|
||||
|
||||
### Data / Analysis — Python
|
||||
|
||||
Python is used for data science and ML-heavy workloads where the Go ecosystem is thin:
|
||||
|
||||
- `pandas`, `numpy` — data manipulation
|
||||
- `pandas-ta`, `ta-lib` — technical indicators
|
||||
- `scikit-learn` — pattern recognition / ML
|
||||
- `statsmodels` — GARCH, time series
|
||||
|
||||
When Python is used, it runs as a **sidecar service** alongside the Go application, exposing a local HTTP/gRPC API.
|
||||
|
||||
### Frontend / Docs — TypeScript + React
|
||||
|
||||
- Docusaurus v3 for documentation
|
||||
- React + shadcn/ui + Tailwind for interactive components within docs
|
||||
|
||||
## Code Conventions
|
||||
|
||||
### Go
|
||||
|
||||
- **`gofmt`** — enforced in CI
|
||||
- **Error handling** — always wrap with `fmt.Errorf("context: %w", err)`, never ignore
|
||||
- **Packages** — flat, purposeful. No circular dependencies. `internal/` for app-private code
|
||||
- **Config** — environment variables via Viper, validated at startup
|
||||
- **Logging** — structured JSON via zerolog, no `fmt.Println` in production code
|
||||
- **Tests** — table-driven tests in `_test.go` files, `testify/assert` for assertions
|
||||
|
||||
```go
|
||||
// Good — explicit error context
|
||||
if err := db.Query(ctx, q); err != nil {
|
||||
return fmt.Errorf("ingestor: fetch candles: %w", err)
|
||||
}
|
||||
|
||||
// Bad — swallowed error
|
||||
db.Query(ctx, q)
|
||||
```
|
||||
|
||||
### Git
|
||||
|
||||
- **Branch naming**: `feat/description`, `fix/description`, `docs/description`
|
||||
- **Commit messages**: conventional commits — `feat:`, `fix:`, `docs:`, `chore:`, `refactor:`
|
||||
- **PRs**: all changes via PR, even from Jarvis — CI must pass before merge
|
||||
- **Main is always deployable**
|
||||
|
||||
## Makefile Targets (per app)
|
||||
|
||||
```makefile
|
||||
build: ## Build binary
|
||||
go build -o bin/server ./cmd/server
|
||||
|
||||
test: ## Run tests
|
||||
go test ./... -race -cover
|
||||
|
||||
lint: ## Run linter
|
||||
golangci-lint run
|
||||
|
||||
docker: ## Build Docker image
|
||||
docker build -t $(APP_NAME):$(VERSION) .
|
||||
|
||||
run: ## Run locally with .env
|
||||
source .env && go run ./cmd/server
|
||||
```
|
||||
|
||||
## Adding a New Application
|
||||
|
||||
1. Create directory `skic-v1-playground/<app-name>/`
|
||||
2. Initialize Go module: `go mod init gitea.lego-cloud.eu/skic-v1-playground/<app-name>`
|
||||
3. Add to `go.work`: `use ./<app-name>`
|
||||
4. Create `.gitea/workflows/build.yml` from the CI template
|
||||
5. Create `Dockerfile` (multi-stage: `golang:1.22-alpine` → `alpine:3.19`)
|
||||
6. Add documentation under `documentation/docs/products/<app-name>/` following C4 structure
|
||||
7. Add to homepage application table
|
||||
+35
-18
@@ -3,29 +3,46 @@ sidebar_position: 1
|
||||
slug: /intro
|
||||
---
|
||||
|
||||
# SKIC Playground Documentation
|
||||
# SKIC Platform
|
||||
|
||||
Welcome to the documentation hub for **SKIC Playground** — a collection of autonomous applications built and maintained by Jarvis (AI agent).
|
||||
**SKIC Playground** is a self-hosted autonomous platform — a collection of applications designed, built, and operated by Jarvis (AI agent) on top of a private infrastructure stack.
|
||||
|
||||
## What is this?
|
||||
## What lives here
|
||||
|
||||
This documentation covers various applications designed, developed, and operated autonomously. Each application has its own section with:
|
||||
import { Badge, Card, CardHeader, CardTitle, CardContent } from '@site/src/components/ui';
|
||||
|
||||
- **Overview** — What the application does and why
|
||||
- **Architecture** — Technical design decisions and system diagrams
|
||||
- **Configuration** — How to set up and configure
|
||||
- **Operations** — How the application runs day-to-day
|
||||
<div className="grid grid-cols-1 gap-3 my-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle>📈 Stock Market Professional</CardTitle>
|
||||
<Badge variant="wip">In Development</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
Monitors forex & stock markets, applies technical analysis to spot patterns, and emits daily trading signals (max 3 positions, 30-minute hold, manual execution).
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
## Applications
|
||||
## How this documentation is structured
|
||||
|
||||
| Application | Status | Description |
|
||||
|---|---|---|
|
||||
| [Stock Market Professional](../applications/stock-market-professional/index.md) | 🚧 In Development | Forex/stock monitoring, technical analysis, and signal generation |
|
||||
| Section | Contents |
|
||||
|---|---|
|
||||
| **Introduction** | This page — platform overview and navigation guide |
|
||||
| **Platform** | Infrastructure, CI/CD, networking, auth |
|
||||
| **Products** | Each autonomous application — architecture, modules, operations |
|
||||
| **Engineering** | Development conventions, monorepo structure, tech stack |
|
||||
| **Runbooks** | Operational procedures, incident response, diagnostics |
|
||||
|
||||
## Technology Stack
|
||||
## Technology at a glance
|
||||
|
||||
- **Infrastructure**: TrueNAS SCALE, Gitea, Docker
|
||||
- **CI/CD**: Gitea Actions
|
||||
- **Documentation**: Docusaurus + pnpm
|
||||
- **Languages**: TypeScript, Python
|
||||
- **AI/ML**: Pattern recognition, technical analysis algorithms
|
||||
| Layer | Technology |
|
||||
|---|---|
|
||||
| Infrastructure | TrueNAS SCALE, Docker, Gitea |
|
||||
| Auth | Keycloak (SSO) |
|
||||
| Registry | Zot (OCI) |
|
||||
| CI/CD | Gitea Actions |
|
||||
| App language | Go (services), Python (data/analysis), TypeScript (frontend) |
|
||||
| Docs | Docusaurus v3 + pnpm + Tailwind + shadcn/ui |
|
||||
| AI operations | Hermes Agent (Jarvis) |
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# CI/CD Pipeline
|
||||
|
||||
All applications follow a single, consistent Gitea Actions pipeline.
|
||||
|
||||
## Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant J as Jarvis / Dev
|
||||
participant G as Gitea
|
||||
participant A as Actions Worker
|
||||
participant R as Zot Registry
|
||||
participant H as TrueNAS Host
|
||||
|
||||
J->>G: git push main
|
||||
G->>A: Trigger workflow
|
||||
A->>A: pnpm/go build + test
|
||||
A->>R: docker push image
|
||||
A->>H: Deploy (restart container)
|
||||
H-->>J: ✅ Live
|
||||
```
|
||||
|
||||
## Workflow Template
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/build.yml
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build
|
||||
run: make build # or go build / pnpm build
|
||||
|
||||
- name: Test
|
||||
run: make test
|
||||
|
||||
- name: Docker build & push
|
||||
if: github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
docker build -t $ZOT_REGISTRY/$APP_NAME:$GITHUB_SHA .
|
||||
docker push $ZOT_REGISTRY/$APP_NAME:$GITHUB_SHA
|
||||
|
||||
- name: Deploy
|
||||
if: github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
ssh deploy@lego-cloud.eu "docker pull && docker restart $APP_NAME"
|
||||
```
|
||||
|
||||
## Gitea Actions Runners
|
||||
|
||||
Runners need to be registered on the Gitea instance. See [Gitea Actions documentation](https://docs.gitea.com/usage/actions/quickstart) for setup.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Platform Overview
|
||||
|
||||
SKIC Playground runs entirely on self-hosted infrastructure at `lego-cloud.eu`.
|
||||
|
||||
## Infrastructure Diagram
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph TrueNAS["TrueNAS SCALE 26.0 — lego-cloud.eu"]
|
||||
direction TB
|
||||
Gitea["Gitea 1.26\nSource + CI/CD\ngitea.lego-cloud.eu"]
|
||||
Zot["Zot Registry\n:30264\nOCI images"]
|
||||
Keycloak["Keycloak\nSSO / Auth\nkeycloak.lego-cloud.eu"]
|
||||
Hermes["Hermes Agent\nAI Operations\n(Jarvis)"]
|
||||
Apps["Application\nContainers"]
|
||||
end
|
||||
|
||||
Dev["Developer / Jarvis"] -->|git push| Gitea
|
||||
Gitea -->|Actions runner| CI["CI/CD Worker"]
|
||||
CI -->|docker push| Zot
|
||||
CI -->|deploy| Apps
|
||||
Zot -->|pull| Apps
|
||||
Keycloak -->|auth| Apps
|
||||
Hermes -->|manages| Gitea
|
||||
Hermes -->|manages| Apps
|
||||
```
|
||||
|
||||
## Services
|
||||
|
||||
| Service | URL | Version | Purpose |
|
||||
|---|---|---|---|
|
||||
| Gitea | gitea.lego-cloud.eu | 1.26.2 | Source control, CI/CD, issue tracking |
|
||||
| Keycloak | keycloak.lego-cloud.eu | — | Authentication, SSO |
|
||||
| Zot | :30264 | — | OCI-compliant container registry |
|
||||
| Hermes | local | — | AI agent (Jarvis) — automation, docs, ops |
|
||||
|
||||
## Storage
|
||||
|
||||
All persistent data lives under `/mnt/lego-cloud-v1-2025-11-28/` on TrueNAS.
|
||||
@@ -0,0 +1,94 @@
|
||||
<mxfile host="Electron" modified="2026-07-16T00:00:00.000Z" agent="Jarvis" version="24.0.0" type="device">
|
||||
<diagram id="smp-pipeline" name="SMP Data Pipeline">
|
||||
<mxGraphModel dx="1422" dy="762" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
|
||||
<!-- Title -->
|
||||
<mxCell id="title" value="Stock Market Professional — Data Pipeline" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=18;fontStyle=1;fontColor=#6366f1;" vertex="1" parent="1">
|
||||
<mxGeometry x="200" y="20" width="760" height="40" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Market Data APIs -->
|
||||
<mxCell id="api" value="Market Data APIs
(Yahoo / Alpha Vantage / Polygon)" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1a1a2e;strokeColor=#6366f1;fontColor=#ffffff;fontSize=11;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="120" width="200" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Data Ingestor -->
|
||||
<mxCell id="ingestor" value="Data Ingestor
Python / APScheduler" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#111827;strokeColor=#818cf8;fontColor=#ffffff;fontSize=11;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="120" width="200" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- TimescaleDB -->
|
||||
<mxCell id="tsdb" value="Market Data Store
SQLite / TimescaleDB" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;fillColor=#0f172a;strokeColor=#6366f1;fontColor=#ffffff;fontSize=11;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="110" width="160" height="80" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Analysis Engine -->
|
||||
<mxCell id="analyser" value="Analysis Engine
pandas-ta / ta-lib" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#111827;strokeColor=#818cf8;fontColor=#ffffff;fontSize=11;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="260" width="200" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Signal Generator -->
|
||||
<mxCell id="signalgen" value="Signal Generator
Max 3/day · 30min hold" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1e1b4b;strokeColor=#818cf8;fontColor=#ffffff;fontSize=11;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="400" width="200" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- News API -->
|
||||
<mxCell id="news" value="Economic Calendar
Forex Factory / Investing.com" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1a1a2e;strokeColor=#f59e0b;fontColor=#ffffff;fontSize=11;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="400" width="200" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Notifier -->
|
||||
<mxCell id="notifier" value="Notifier
Discord Webhook" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#111827;strokeColor=#818cf8;fontColor=#ffffff;fontSize=11;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="640" y="400" width="160" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Discord -->
|
||||
<mxCell id="discord" value="Discord
Daily Trading Plan" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#1a1a2e;strokeColor=#6366f1;fontColor=#ffffff;fontSize=11;" vertex="1" parent="1">
|
||||
<mxGeometry x="880" y="400" width="160" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Trader -->
|
||||
<mxCell id="trader" value="👤 Trader
Manual Execution" style="ellipse;whiteSpace=wrap;html=1;fillColor=#0f172a;strokeColor=#10b981;fontColor=#ffffff;fontSize=11;fontStyle=1;" vertex="1" parent="1">
|
||||
<mxGeometry x="893" y="540" width="140" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
|
||||
<!-- Arrows -->
|
||||
<mxCell id="e1" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;exitX=1;exitY=0.5;entryX=0;entryY=0.5;strokeColor=#6366f1;fontColor=#a5b4fc;fontSize=10;" edge="1" source="api" target="ingestor" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="e1l" value="OHLCV" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" vertex="1" connectable="0" parent="e1"><mxGeometry relative="0.5" as="geometry"/></mxCell>
|
||||
</mxCell>
|
||||
<mxCell id="e2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#818cf8;fontSize=10;" edge="1" source="ingestor" target="tsdb" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="e2l" value="write candles" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=10;" vertex="1" connectable="0" parent="e2"><mxGeometry relative="0.5" as="geometry"/></mxCell>
|
||||
</mxCell>
|
||||
<mxCell id="e3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#818cf8;fontSize=10;" edge="1" source="tsdb" target="analyser" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="e3l" value="read candles" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=10;" vertex="1" connectable="0" parent="e3"><mxGeometry relative="0.5" as="geometry"/></mxCell>
|
||||
</mxCell>
|
||||
<mxCell id="e4" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#818cf8;fontSize=10;" edge="1" source="analyser" target="signalgen" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="e4l" value="scored snapshots" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=10;" vertex="1" connectable="0" parent="e4"><mxGeometry relative="0.5" as="geometry"/></mxCell>
|
||||
</mxCell>
|
||||
<mxCell id="e5" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#f59e0b;fontSize=10;" edge="1" source="news" target="signalgen" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="e5l" value="news filter" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=10;" vertex="1" connectable="0" parent="e5"><mxGeometry relative="0.5" as="geometry"/></mxCell>
|
||||
</mxCell>
|
||||
<mxCell id="e6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#818cf8;fontSize=10;" edge="1" source="signalgen" target="notifier" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="e6l" value="≤3 signals" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=10;" vertex="1" connectable="0" parent="e6"><mxGeometry relative="0.5" as="geometry"/></mxCell>
|
||||
</mxCell>
|
||||
<mxCell id="e7" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#6366f1;fontSize=10;" edge="1" source="notifier" target="discord" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="e7l" value="daily plan" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=10;" vertex="1" connectable="0" parent="e7"><mxGeometry relative="0.5" as="geometry"/></mxCell>
|
||||
</mxCell>
|
||||
<mxCell id="e8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;strokeColor=#10b981;fontSize=10;" edge="1" source="discord" target="trader" parent="1">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
<mxCell id="e8l" value="reads signals" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;fontSize=10;" vertex="1" connectable="0" parent="e8"><mxGeometry relative="0.5" as="geometry"/></mxCell>
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# L3 — Components
|
||||
|
||||
> **C4 Level 3**: Zooms into the key containers and shows their internal modules, classes, and responsibilities.
|
||||
|
||||
:::info C4 Model — Level 3
|
||||
The Component diagram answers: *What are the major structural elements inside each container?*
|
||||
:::
|
||||
|
||||
## Analysis Engine — Component Diagram
|
||||
|
||||
```mermaid
|
||||
C4Component
|
||||
title Components — Analysis Engine
|
||||
|
||||
Container_Ext(tsdb, "Market Data Store", "SQLite / TimescaleDB", "OHLCV candles")
|
||||
Container_Ext(signalGen, "Signal Generator", "Python", "Consumes indicator snapshots")
|
||||
|
||||
Container_Boundary(analyser, "Analysis Engine") {
|
||||
Component(candleLoader, "Candle Loader", "pandas", "Loads recent OHLCV windows from DB for each symbol and timeframe.")
|
||||
Component(trendEngine, "Trend Engine", "pandas-ta", "Computes EMA, MACD, ADX, Parabolic SAR.")
|
||||
Component(momentumEngine, "Momentum Engine", "pandas-ta", "Computes RSI, Stochastic, CCI, Williams %R.")
|
||||
Component(volatilityEngine, "Volatility Engine", "pandas-ta", "Computes Bollinger Bands, ATR, Keltner Channels.")
|
||||
Component(volumeEngine, "Volume Engine", "pandas-ta", "Computes VWAP, OBV, Volume Profile.")
|
||||
Component(patternRecog, "Pattern Recogniser", "ta-lib / custom", "Detects candlestick and chart patterns.")
|
||||
Component(confluenceScorer, "Confluence Scorer", "Python", "Weights and combines indicator signals into a 0–1 confluence score.")
|
||||
Component(snapshotWriter, "Snapshot Writer", "pandas / SQLAlchemy", "Persists indicator snapshots to DB for the signal generator.")
|
||||
}
|
||||
|
||||
Rel(candleLoader, tsdb, "Reads OHLCV", "SQL")
|
||||
Rel(trendEngine, candleLoader, "Reads candle frame")
|
||||
Rel(momentumEngine, candleLoader, "Reads candle frame")
|
||||
Rel(volatilityEngine, candleLoader, "Reads candle frame")
|
||||
Rel(volumeEngine, candleLoader, "Reads candle frame")
|
||||
Rel(patternRecog, candleLoader, "Reads candle frame")
|
||||
Rel(confluenceScorer, trendEngine, "Reads signals")
|
||||
Rel(confluenceScorer, momentumEngine, "Reads signals")
|
||||
Rel(confluenceScorer, volatilityEngine, "Reads signals")
|
||||
Rel(confluenceScorer, volumeEngine, "Reads signals")
|
||||
Rel(confluenceScorer, patternRecog, "Reads patterns")
|
||||
Rel(snapshotWriter, confluenceScorer, "Reads scored snapshot")
|
||||
Rel(snapshotWriter, tsdb, "Writes snapshots", "SQL")
|
||||
Rel(signalGen, snapshotWriter, "Reads snapshots", "SQL")
|
||||
```
|
||||
|
||||
## Signal Generator — Component Diagram
|
||||
|
||||
```mermaid
|
||||
C4Component
|
||||
title Components — Signal Generator
|
||||
|
||||
Container_Ext(tsdb, "Market Data Store", "TimescaleDB", "Indicator snapshots")
|
||||
Container_Ext(newsApi, "Economic Calendar API", "REST", "High-impact events")
|
||||
Container_Ext(notifier, "Notifier", "Python", "Receives final signals")
|
||||
|
||||
Container_Boundary(signalGen, "Signal Generator") {
|
||||
Component(snapshotReader, "Snapshot Reader", "SQLAlchemy", "Loads latest indicator snapshots per symbol.")
|
||||
Component(biasFilter, "Bias Filter", "Python", "Determines directional bias (bullish/bearish/neutral) per pair from higher-TF trend.")
|
||||
Component(setupScanner, "Setup Scanner", "Python", "Identifies candidate setups where confluence score ≥ 0.65.")
|
||||
Component(riskCalc, "Risk Calculator", "Python", "Calculates entry, stop-loss, take-profit; validates R:R ≥ 1.5.")
|
||||
Component(newsFilter, "News Filter", "Python / requests", "Rejects signals within 30 min of high-impact economic events.")
|
||||
Component(correlFilter, "Correlation Filter", "Python", "Prevents conflicting signals on correlated pairs (e.g. EUR/USD + GBP/USD both long).")
|
||||
Component(dailyLimitGuard, "Daily Limit Guard", "Python", "Enforces max 3 signals per day hard cap.")
|
||||
Component(signalAssembler, "Signal Assembler", "Python", "Packages final signals with metadata, reasoning, and time window.")
|
||||
}
|
||||
|
||||
Rel(snapshotReader, tsdb, "Reads snapshots", "SQL")
|
||||
Rel(biasFilter, snapshotReader, "Reads higher-TF data")
|
||||
Rel(setupScanner, snapshotReader, "Reads scored snapshots")
|
||||
Rel(setupScanner, biasFilter, "Applies directional filter")
|
||||
Rel(riskCalc, setupScanner, "Calculates levels per setup")
|
||||
Rel(newsFilter, newsApi, "Fetches upcoming events", "REST")
|
||||
Rel(newsFilter, riskCalc, "Filters out news-window setups")
|
||||
Rel(correlFilter, newsFilter, "Deduplicates correlated setups")
|
||||
Rel(dailyLimitGuard, correlFilter, "Enforces ≤3 cap")
|
||||
Rel(signalAssembler, dailyLimitGuard, "Assembles final signals")
|
||||
Rel(notifier, signalAssembler, "Receives signals for delivery")
|
||||
```
|
||||
|
||||
## Data Ingestor — Component Breakdown
|
||||
|
||||
| Component | Responsibility |
|
||||
|---|---|
|
||||
| **Source Router** | Selects primary/fallback data source per symbol |
|
||||
| **REST Poller** | Fetches 1m candles from REST API on schedule |
|
||||
| **WS Stream Client** | Maintains WebSocket connection for real-time tick data |
|
||||
| **Normaliser** | Maps source-specific field names → unified OHLCV schema |
|
||||
| **Gap Detector** | Identifies missing candles, triggers backfill |
|
||||
| **Backfiller** | Fetches historical data to fill detected gaps |
|
||||
| **Rate Limiter** | Tracks and respects per-API-key rate limits |
|
||||
| **Health Reporter** | Emits data freshness metrics; alerts on stale data |
|
||||
|
||||
## Confluence Scoring — Detail
|
||||
|
||||
```python
|
||||
# Scoring weights (sum = 1.0)
|
||||
WEIGHTS = {
|
||||
'trend_alignment': 0.30, # EMA cross, MACD, ADX
|
||||
'momentum': 0.25, # RSI, Stochastic, CCI
|
||||
'volume': 0.20, # VWAP, OBV
|
||||
'pattern': 0.15, # Candlestick / chart patterns
|
||||
'key_level': 0.10, # Support/resistance proximity
|
||||
}
|
||||
|
||||
MINIMUM_CONFLUENCE = 0.65 # Hard threshold
|
||||
MINIMUM_RR_RATIO = 1.5 # Risk/reward hard floor
|
||||
MAX_DAILY_SIGNALS = 3 # Hard daily cap
|
||||
MAX_HOLD_MINUTES = 30 # Hard position time limit
|
||||
```
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# L2 — Containers
|
||||
|
||||
> **C4 Level 2**: Zooms into Stock Market Professional and shows its deployable units — services, databases, and schedulers.
|
||||
|
||||
:::info C4 Model — Level 2
|
||||
The Container diagram answers: *What are the high-level technical building blocks, and how do they talk to each other?*
|
||||
:::
|
||||
|
||||
## Container Diagram
|
||||
|
||||
```mermaid
|
||||
C4Container
|
||||
title Container Diagram — Stock Market Professional
|
||||
|
||||
Person(trader, "Trader", "Reviews signals, executes manually")
|
||||
|
||||
System_Boundary(smp, "Stock Market Professional") {
|
||||
Container(ingestor, "Data Ingestor", "Python / APScheduler", "Polls market data APIs on schedule, normalises OHLCV candles, detects gaps.")
|
||||
Container(analyser, "Analysis Engine", "Python / pandas-ta", "Applies technical indicators and pattern recognition on latest candle data.")
|
||||
Container(signalGen, "Signal Generator", "Python", "Scores setups by confluence, filters by risk rules, produces ≤3 daily signals.")
|
||||
Container(scheduler, "Job Scheduler", "APScheduler / cron", "Orchestrates pipeline runs: ingest every minute, analyse every 5 min, generate plan pre-market.")
|
||||
ContainerDb(tsdb, "Market Data Store", "SQLite / TimescaleDB", "Stores normalised OHLCV candles, indicator snapshots, and signal history.")
|
||||
Container(notifier, "Notifier", "Python / Discord Webhook", "Formats and delivers the daily trading plan and intraday alerts.")
|
||||
}
|
||||
|
||||
System_Ext(marketData, "Market Data APIs", "Yahoo Finance, Alpha Vantage, Polygon")
|
||||
System_Ext(discord, "Discord", "Signal delivery")
|
||||
System_Ext(newsApi, "Economic Calendar API", "Forex Factory, Investing.com")
|
||||
|
||||
Rel(scheduler, ingestor, "Triggers", "Internal call")
|
||||
Rel(scheduler, analyser, "Triggers", "Internal call")
|
||||
Rel(scheduler, signalGen, "Triggers", "Internal call")
|
||||
Rel(ingestor, marketData, "Fetches OHLCV", "REST / WS")
|
||||
Rel(ingestor, tsdb, "Writes candles", "SQL")
|
||||
Rel(analyser, tsdb, "Reads candles", "SQL")
|
||||
Rel(analyser, tsdb, "Writes indicator snapshots", "SQL")
|
||||
Rel(signalGen, tsdb, "Reads snapshots", "SQL")
|
||||
Rel(signalGen, newsApi, "Checks economic calendar", "REST")
|
||||
Rel(signalGen, notifier, "Passes signals", "In-process")
|
||||
Rel(notifier, discord, "Posts daily plan", "Webhook")
|
||||
Rel(trader, discord, "Reads signals", "Discord UI")
|
||||
```
|
||||
|
||||
## Containers Inventory
|
||||
|
||||
| Container | Technology | Responsibility |
|
||||
|---|---|---|
|
||||
| **Data Ingestor** | Python, requests, websockets | Poll & normalise market data |
|
||||
| **Analysis Engine** | Python, pandas, pandas-ta, ta-lib | Technical indicators + pattern recognition |
|
||||
| **Signal Generator** | Python | Confluence scoring, risk rules, signal assembly |
|
||||
| **Job Scheduler** | APScheduler | Pipeline orchestration & timing |
|
||||
| **Market Data Store** | SQLite (dev) / TimescaleDB (prod) | Persist OHLCV, indicators, signals |
|
||||
| **Notifier** | Python, Discord Webhook | Format and deliver signals |
|
||||
|
||||
## Deployment
|
||||
|
||||
All containers run as a single **Python process** in development (scheduler + all modules), and can be split into separate Docker containers in production:
|
||||
|
||||
```
|
||||
docker/
|
||||
├── ingestor/ # Data poller container
|
||||
├── analyser/ # Analysis worker container
|
||||
├── signal-gen/ # Signal generation + notifier
|
||||
└── timescaledb/ # Database container
|
||||
```
|
||||
|
||||
The CI pipeline (Gitea Actions) builds images and pushes to the Zot container registry at `lego-cloud.eu:30264`.
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# L1 — System Context
|
||||
|
||||
> **C4 Level 1**: Shows how Stock Market Professional fits into the world — who uses it, and what external systems it depends on.
|
||||
|
||||
:::info C4 Model — Level 1
|
||||
The Context diagram answers: *What does this system do, and who / what interacts with it?*
|
||||
:::
|
||||
|
||||
## Context Diagram
|
||||
|
||||
```mermaid
|
||||
C4Context
|
||||
title System Context — Stock Market Professional
|
||||
|
||||
Person(trader, "Trader (Lego)", "Human operator who reviews signals and executes trades manually.")
|
||||
|
||||
System(smp, "Stock Market Professional", "Monitors markets, runs technical analysis, and emits daily trading signals.")
|
||||
|
||||
System_Ext(marketData, "Market Data Provider", "Real-time & historical price feeds (Yahoo Finance, Alpha Vantage, Polygon.io)")
|
||||
System_Ext(discord, "Discord", "Signal delivery channel — daily trading plans posted to a server.")
|
||||
System_Ext(broker, "Broker / Trading Platform", "Where the trader manually executes trades (e.g. Interactive Brokers, MetaTrader).")
|
||||
System_Ext(newsApi, "Economic Calendar / News API", "High-impact event feed used to filter signals around news releases.")
|
||||
|
||||
Rel(trader, smp, "Reviews signals and trading plan")
|
||||
Rel(smp, marketData, "Fetches OHLCV data", "REST / WebSocket")
|
||||
Rel(smp, discord, "Posts daily plan & alerts", "Webhook / Bot API")
|
||||
Rel(smp, newsApi, "Queries upcoming events", "REST API")
|
||||
Rel(trader, broker, "Executes trades manually", "Web UI / API")
|
||||
```
|
||||
|
||||
## Actors & Systems
|
||||
|
||||
| Entity | Type | Role |
|
||||
|---|---|---|
|
||||
| Trader (Lego) | Person | Reviews daily signal plan, executes trades manually |
|
||||
| Market Data Provider | External System | Source of truth for price data |
|
||||
| Discord | External System | Primary delivery channel for signals |
|
||||
| Broker / Trading Platform | External System | Where actual trades happen (out of scope) |
|
||||
| Economic Calendar API | External System | News filter to avoid high-impact event windows |
|
||||
|
||||
## Key Constraints
|
||||
|
||||
- **No auto-trading** — all order execution is manual
|
||||
- **Signal limit** — max **3 signals per day** to enforce quality
|
||||
- **Hold time** — max **30 minutes** per position (scalping / intraday)
|
||||
- **Manual review** — trader has final say on all entries
|
||||
@@ -0,0 +1,72 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# Data Monitoring
|
||||
|
||||
## Overview
|
||||
|
||||
The data monitoring module is responsible for acquiring, normalizing, and storing market data that feeds into the analysis engine.
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Latency**: Near real-time (< 1 minute delay)
|
||||
- **Coverage**: Forex major pairs + selected stocks/indices
|
||||
- **Granularity**: 1-minute candles, tick data where available
|
||||
- **History**: Minimum 30 days rolling window for backtesting
|
||||
- **Reliability**: Automatic reconnection, data gap detection
|
||||
|
||||
## Watched Instruments
|
||||
|
||||
### Forex Pairs (Initial)
|
||||
| Pair | Session | Notes |
|
||||
|---|---|---|
|
||||
| EUR/USD | London + NY | Most liquid, tight spreads |
|
||||
| GBP/USD | London + NY | High volatility windows |
|
||||
| USD/JPY | Tokyo + London | Trend-following opportunities |
|
||||
| AUD/USD | Sydney + London | Commodity correlation |
|
||||
|
||||
### Indices (Future)
|
||||
| Index | Session | Notes |
|
||||
|---|---|---|
|
||||
| S&P 500 (SPX) | NY | Broad market sentiment |
|
||||
| NASDAQ 100 (NDX) | NY | Tech sector proxy |
|
||||
|
||||
## Data Pipeline
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ API Source │────▶│ Normalizer │────▶│ Storage │
|
||||
│ │ │ │ │ │
|
||||
│ • REST poll │ │ • OHLCV fmt │ │ • SQLite/TS │
|
||||
│ • WebSocket │ │ • Timestamps │ │ • Partitioned│
|
||||
│ • Fallback │ │ • Validation │ │ • Indexed │
|
||||
└──────────────┘ └──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
## Data Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE candles (
|
||||
id INTEGER PRIMARY KEY,
|
||||
symbol TEXT NOT NULL,
|
||||
timeframe TEXT NOT NULL, -- '1m', '5m', '15m', '1h'
|
||||
timestamp DATETIME NOT NULL,
|
||||
open REAL NOT NULL,
|
||||
high REAL NOT NULL,
|
||||
low REAL NOT NULL,
|
||||
close REAL NOT NULL,
|
||||
volume REAL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(symbol, timeframe, timestamp)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_candles_symbol_time ON candles(symbol, timeframe, timestamp DESC);
|
||||
```
|
||||
|
||||
## Health Checks
|
||||
|
||||
- Data freshness monitoring (alert if > 5 min stale)
|
||||
- Gap detection and backfill
|
||||
- Source failover (primary → secondary API)
|
||||
- Rate limit tracking per API key
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Stock Market Professional
|
||||
|
||||
> Autonomous stock/forex monitoring, technical analysis, and signal generation system.
|
||||
|
||||
## Purpose
|
||||
|
||||
Stock Market Professional is designed to:
|
||||
|
||||
1. **Monitor** real-time market data (stocks, forex pairs)
|
||||
2. **Analyze** price action using technical and statistical methods to spot patterns
|
||||
3. **Signal** consumers with daily trading plans
|
||||
|
||||
## Trading Strategy Constraints
|
||||
|
||||
| Parameter | Value |
|
||||
|---|---|
|
||||
| Max daily purchases | **3** |
|
||||
| Max hold time | **30 minutes** |
|
||||
| Execution | **Manual** (signals only — no auto-trading) |
|
||||
| Market focus | Forex / Short-term stock positions |
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
||||
│ Data Ingestion │────▶│ Analysis Engine │────▶│ Signal Generator│
|
||||
│ │ │ │ │ │
|
||||
│ • Market feeds │ │ • Technical ind. │ │ • Daily plans │
|
||||
│ • Price streams │ │ • Pattern recog. │ │ • Entry/Exit │
|
||||
│ • Volume data │ │ • Statistical │ │ • Risk mgmt │
|
||||
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Consumers │
|
||||
│ (Discord/ │
|
||||
│ Webhook) │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
## Modules
|
||||
|
||||
- [Architecture & Design](./overview/architecture)
|
||||
- [Data Monitoring](./data-monitoring/)
|
||||
- [Technical Analysis](./technical-analysis/)
|
||||
- [Signal Generation](./signal-generation/)
|
||||
@@ -0,0 +1,95 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Architecture & Design
|
||||
|
||||
## System Design
|
||||
|
||||
Stock Market Professional follows a **pipeline architecture** with three distinct stages:
|
||||
|
||||
### 1. Data Layer (Ingestion)
|
||||
|
||||
Responsible for acquiring and normalizing market data from multiple sources.
|
||||
|
||||
**Requirements:**
|
||||
- Real-time or near-real-time price data (1-minute candles minimum)
|
||||
- Support for multiple forex pairs and stock tickers
|
||||
- Historical data for backtesting (minimum 30 days)
|
||||
- Volume and order book data where available
|
||||
|
||||
**Potential Data Sources:**
|
||||
- Yahoo Finance API (free tier)
|
||||
- Alpha Vantage API
|
||||
- Twelve Data API
|
||||
- Interactive Brokers TWS API
|
||||
- Polygon.io
|
||||
|
||||
### 2. Analysis Layer (Processing)
|
||||
|
||||
Applies technical indicators, statistical models, and pattern recognition.
|
||||
|
||||
**Technical Indicators:**
|
||||
- Moving Averages (SMA, EMA, WMA)
|
||||
- RSI (Relative Strength Index)
|
||||
- MACD (Moving Average Convergence Divergence)
|
||||
- Bollinger Bands
|
||||
- Fibonacci Retracements
|
||||
- Volume-Weighted Average Price (VWAP)
|
||||
- Stochastic Oscillator
|
||||
|
||||
**Pattern Recognition:**
|
||||
- Candlestick patterns (Doji, Hammer, Engulfing, etc.)
|
||||
- Chart patterns (Head & Shoulders, Double Top/Bottom, Triangles)
|
||||
- Support/Resistance levels
|
||||
- Breakout detection
|
||||
|
||||
**Statistical Analysis:**
|
||||
- Volatility clustering (GARCH models)
|
||||
- Mean reversion detection
|
||||
- Momentum scoring
|
||||
- Correlation analysis between pairs
|
||||
|
||||
### 3. Signal Layer (Output)
|
||||
|
||||
Generates actionable trading signals with strict constraints.
|
||||
|
||||
**Signal Format:**
|
||||
```json
|
||||
{
|
||||
"date": "2026-07-16",
|
||||
"signals": [
|
||||
{
|
||||
"pair": "EUR/USD",
|
||||
"direction": "BUY",
|
||||
"entry_price": 1.0892,
|
||||
"stop_loss": 1.0875,
|
||||
"take_profit": 1.0920,
|
||||
"confidence": 0.82,
|
||||
"reasoning": "Bullish engulfing + RSI oversold bounce + VWAP support",
|
||||
"time_window": "09:00-09:30 UTC",
|
||||
"max_hold_minutes": 30
|
||||
}
|
||||
],
|
||||
"max_signals_per_day": 3
|
||||
}
|
||||
```
|
||||
|
||||
## Technology Choices
|
||||
|
||||
| Component | Technology | Reason |
|
||||
|---|---|---|
|
||||
| Language | Python 3.11+ | Rich ecosystem for finance/ML |
|
||||
| Data | pandas, numpy | Industry standard for time series |
|
||||
| Indicators | ta-lib, pandas-ta | Comprehensive TA libraries |
|
||||
| ML/Stats | scikit-learn, statsmodels | Pattern recognition, GARCH |
|
||||
| Scheduling | APScheduler / cron | Regular data pulls and analysis |
|
||||
| Delivery | Discord webhook / API | Direct consumer notification |
|
||||
| Storage | SQLite / TimescaleDB | Time-series optimized |
|
||||
|
||||
## Deployment
|
||||
|
||||
- Containerized (Docker)
|
||||
- Runs on TrueNAS infrastructure
|
||||
- CI/CD via Gitea Actions
|
||||
- Health monitoring and alerting
|
||||
@@ -0,0 +1,130 @@
|
||||
---
|
||||
sidebar_position: 4
|
||||
---
|
||||
|
||||
# Signal Generation
|
||||
|
||||
## Overview
|
||||
|
||||
The signal generation module produces actionable trading plans delivered to consumers daily. Signals are constrained by strict risk management rules.
|
||||
|
||||
## Constraints
|
||||
|
||||
| Rule | Value | Rationale |
|
||||
|---|---|---|
|
||||
| Max signals per day | **3** | Quality over quantity |
|
||||
| Max hold time | **30 minutes** | Scalping/day-trade focus |
|
||||
| Execution | **Manual** | Human confirms and executes |
|
||||
| Min confluence score | **0.65** | High-probability setups only |
|
||||
|
||||
## Signal Structure
|
||||
|
||||
Each signal contains:
|
||||
|
||||
```typescript
|
||||
interface TradingSignal {
|
||||
// Identification
|
||||
id: string;
|
||||
timestamp: string;
|
||||
pair: string;
|
||||
|
||||
// Direction
|
||||
direction: 'BUY' | 'SELL';
|
||||
|
||||
// Levels
|
||||
entry_price: number;
|
||||
stop_loss: number;
|
||||
take_profit: number;
|
||||
|
||||
// Meta
|
||||
confidence: number; // 0.0 - 1.0
|
||||
reasoning: string[]; // List of confirming factors
|
||||
time_window: string; // Optimal entry window
|
||||
max_hold_minutes: number; // Always ≤ 30
|
||||
|
||||
// Risk
|
||||
risk_reward_ratio: number; // Minimum 1:1.5
|
||||
position_size_pct: number; // % of capital suggested
|
||||
}
|
||||
```
|
||||
|
||||
## Daily Plan Format
|
||||
|
||||
Delivered each trading day before market open:
|
||||
|
||||
```markdown
|
||||
## 📊 Daily Trading Plan — 2026-07-16
|
||||
|
||||
### Market Bias
|
||||
- EUR/USD: Bullish (1H EMA trend up)
|
||||
- GBP/USD: Neutral (ranging)
|
||||
|
||||
### Signals (Max 3)
|
||||
|
||||
#### Signal 1: EUR/USD BUY
|
||||
- **Entry**: 1.0892 (on pullback to VWAP)
|
||||
- **Stop Loss**: 1.0875 (-17 pips)
|
||||
- **Take Profit**: 1.0920 (+28 pips)
|
||||
- **R:R**: 1:1.65
|
||||
- **Window**: 09:00-09:30 UTC
|
||||
- **Confidence**: 82%
|
||||
- **Reasoning**:
|
||||
- Bullish engulfing on 5m
|
||||
- RSI bouncing from 35
|
||||
- VWAP support holding
|
||||
- London session momentum
|
||||
|
||||
#### Signal 2: ...
|
||||
|
||||
### Key Levels to Watch
|
||||
- EUR/USD: Support 1.0870, Resistance 1.0935
|
||||
- GBP/USD: Support 1.2640, Resistance 1.2710
|
||||
|
||||
### Risk Notes
|
||||
- ⚠️ NFP data release at 13:30 UTC — avoid new positions 30min before
|
||||
- Volume expected to increase London/NY overlap (13:00-16:00 UTC)
|
||||
```
|
||||
|
||||
## Delivery Channels
|
||||
|
||||
| Channel | Method | Timing |
|
||||
|---|---|---|
|
||||
| Discord | Webhook/Bot message | Pre-market (06:00 UTC) |
|
||||
| API | REST endpoint | On-demand |
|
||||
| Email | SMTP notification | Optional digest |
|
||||
|
||||
## Signal Validation Rules
|
||||
|
||||
Before a signal is emitted, it must pass:
|
||||
|
||||
1. **Confluence check** — Score ≥ 0.65
|
||||
2. **Risk/Reward check** — R:R ≥ 1:1.5
|
||||
3. **Time filter** — Within active session hours
|
||||
4. **Correlation filter** — No conflicting signals on correlated pairs
|
||||
5. **News filter** — No high-impact events within hold window
|
||||
6. **Daily limit** — Max 3 signals not exceeded
|
||||
7. **Spread check** — Current spread within acceptable range
|
||||
|
||||
## Performance Tracking
|
||||
|
||||
Track all signals for continuous improvement:
|
||||
|
||||
```sql
|
||||
CREATE TABLE signal_performance (
|
||||
signal_id TEXT PRIMARY KEY,
|
||||
pair TEXT,
|
||||
direction TEXT,
|
||||
entry_price REAL,
|
||||
exit_price REAL,
|
||||
pnl_pips REAL,
|
||||
hold_time_minutes INTEGER,
|
||||
outcome TEXT, -- 'TP_HIT', 'SL_HIT', 'TIME_EXIT', 'MANUAL_EXIT'
|
||||
created_at DATETIME
|
||||
);
|
||||
```
|
||||
|
||||
**Target Metrics:**
|
||||
- Win rate: > 55%
|
||||
- Average R:R achieved: > 1.3
|
||||
- Max consecutive losses: < 5
|
||||
- Monthly Sharpe: > 1.5
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
sidebar_position: 3
|
||||
---
|
||||
|
||||
# Technical Analysis
|
||||
|
||||
## Overview
|
||||
|
||||
The technical analysis module processes raw market data through multiple analytical lenses to identify high-probability trading setups within the 30-minute hold constraint.
|
||||
|
||||
## Analysis Categories
|
||||
|
||||
### 1. Trend Indicators
|
||||
|
||||
| Indicator | Parameters | Signal |
|
||||
|---|---|---|
|
||||
| EMA Cross | 9/21 periods | Bullish/Bearish cross |
|
||||
| MACD | 12/26/9 | Histogram divergence |
|
||||
| ADX | 14 periods | Trend strength > 25 |
|
||||
| Parabolic SAR | 0.02/0.2 | Trend reversal dots |
|
||||
|
||||
### 2. Momentum Indicators
|
||||
|
||||
| Indicator | Parameters | Signal |
|
||||
|---|---|---|
|
||||
| RSI | 14 periods | Oversold < 30, Overbought > 70 |
|
||||
| Stochastic | 14/3/3 | %K/%D crossover |
|
||||
| CCI | 20 periods | Extreme readings ±200 |
|
||||
| Williams %R | 14 periods | Reversal zones |
|
||||
|
||||
### 3. Volatility Indicators
|
||||
|
||||
| Indicator | Parameters | Signal |
|
||||
|---|---|---|
|
||||
| Bollinger Bands | 20/2σ | Band squeeze/expansion |
|
||||
| ATR | 14 periods | Position sizing, stop distance |
|
||||
| Keltner Channels | 20/1.5 | Breakout confirmation |
|
||||
|
||||
### 4. Volume Analysis
|
||||
|
||||
| Indicator | Parameters | Signal |
|
||||
|---|---|---|
|
||||
| VWAP | Session | Price vs fair value |
|
||||
| OBV | Cumulative | Divergence from price |
|
||||
| Volume Profile | Session | Key levels, POC |
|
||||
|
||||
## Pattern Recognition
|
||||
|
||||
### Candlestick Patterns (1-5 bar)
|
||||
- **Reversal**: Hammer, Shooting Star, Engulfing, Morning/Evening Star
|
||||
- **Continuation**: Three White Soldiers, Rising/Falling Three Methods
|
||||
- **Indecision**: Doji, Spinning Top, Harami
|
||||
|
||||
### Chart Patterns (Multi-bar)
|
||||
- Double Top/Bottom
|
||||
- Head & Shoulders
|
||||
- Ascending/Descending Triangles
|
||||
- Bull/Bear Flags
|
||||
- Wedges
|
||||
|
||||
## Confluence Scoring
|
||||
|
||||
Signals are scored based on **confluence** — multiple indicators agreeing:
|
||||
|
||||
```python
|
||||
def calculate_confluence_score(signals: list[IndicatorSignal]) -> float:
|
||||
"""
|
||||
Score 0.0 - 1.0 based on how many indicators agree.
|
||||
|
||||
Weights:
|
||||
- Trend alignment: 0.30
|
||||
- Momentum confirmation: 0.25
|
||||
- Volume confirmation: 0.20
|
||||
- Pattern match: 0.15
|
||||
- Key level proximity: 0.10
|
||||
"""
|
||||
weights = {
|
||||
'trend': 0.30,
|
||||
'momentum': 0.25,
|
||||
'volume': 0.20,
|
||||
'pattern': 0.15,
|
||||
'level': 0.10
|
||||
}
|
||||
|
||||
score = sum(
|
||||
weights[s.category] * s.strength
|
||||
for s in signals
|
||||
if s.direction == consensus_direction(signals)
|
||||
)
|
||||
return min(score, 1.0)
|
||||
```
|
||||
|
||||
**Minimum confluence threshold for signal generation: 0.65**
|
||||
|
||||
## Timeframe Analysis
|
||||
|
||||
Given the 30-minute max hold constraint:
|
||||
|
||||
| Timeframe | Purpose |
|
||||
|---|---|
|
||||
| 1-minute | Entry timing, micro-structure |
|
||||
| 5-minute | Primary signal generation |
|
||||
| 15-minute | Trend context, S/R levels |
|
||||
| 1-hour | Bias direction (trend filter) |
|
||||
|
||||
## Backtesting Requirements
|
||||
|
||||
- Minimum 30 days historical data
|
||||
- Walk-forward optimization
|
||||
- Out-of-sample validation
|
||||
- Maximum drawdown tracking
|
||||
- Sharpe ratio > 1.5 target
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# Runbooks
|
||||
|
||||
Operational procedures for SKIC Playground services.
|
||||
|
||||
:::note
|
||||
Runbooks are living documents. Update them when procedures change or new issues are discovered.
|
||||
:::
|
||||
|
||||
## Incident Severity
|
||||
|
||||
| Level | Definition | Response Time |
|
||||
|---|---|---|
|
||||
| P0 | Platform down / all services unreachable | Immediate |
|
||||
| P1 | Single critical service down | < 15 min |
|
||||
| P2 | Degraded performance / non-critical service down | < 1 hour |
|
||||
| P3 | Minor issue / cosmetic | Next working session |
|
||||
|
||||
## Common Procedures
|
||||
|
||||
### Restart a container
|
||||
|
||||
```bash
|
||||
# SSH to TrueNAS
|
||||
ssh root@lego-cloud.eu
|
||||
|
||||
# List running containers
|
||||
docker ps
|
||||
|
||||
# Restart
|
||||
docker restart <container-name>
|
||||
|
||||
# Check logs
|
||||
docker logs -f --tail 100 <container-name>
|
||||
```
|
||||
|
||||
### Check Gitea Actions runner status
|
||||
|
||||
```bash
|
||||
# On the runner host
|
||||
systemctl status gitea-runner
|
||||
|
||||
# Restart if needed
|
||||
systemctl restart gitea-runner
|
||||
```
|
||||
|
||||
### Force re-deploy from latest image
|
||||
|
||||
```bash
|
||||
docker pull <registry>/<image>:<tag>
|
||||
docker stop <container>
|
||||
docker rm <container>
|
||||
docker run -d --name <container> ... <image>:<tag>
|
||||
```
|
||||
|
||||
## Application-specific runbooks
|
||||
|
||||
Individual application runbooks will be added here as applications go into production.
|
||||
@@ -32,6 +32,16 @@ const config: Config = {
|
||||
|
||||
plugins: [
|
||||
'docusaurus-plugin-drawio',
|
||||
async function tailwindPlugin() {
|
||||
return {
|
||||
name: 'docusaurus-tailwindcss',
|
||||
configurePostCSS(postcssOptions: { plugins: unknown[] }) {
|
||||
postcssOptions.plugins.push(require('tailwindcss'));
|
||||
postcssOptions.plugins.push(require('autoprefixer'));
|
||||
return postcssOptions;
|
||||
},
|
||||
};
|
||||
},
|
||||
],
|
||||
|
||||
presets: [
|
||||
|
||||
+10
-2
@@ -18,20 +18,28 @@
|
||||
"@docusaurus/core": "3.10.2",
|
||||
"@docusaurus/faster": "3.10.2",
|
||||
"@docusaurus/preset-classic": "3.10.2",
|
||||
"@docusaurus/theme-common": "^3.10.2",
|
||||
"@docusaurus/theme-mermaid": "^3.10.2",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"clsx": "^2.0.0",
|
||||
"@radix-ui/react-slot": "^1.3.0",
|
||||
"@radix-ui/react-tooltip": "^1.2.12",
|
||||
"clsx": "^2.1.1",
|
||||
"docusaurus-plugin-drawio": "^0.4.0",
|
||||
"prism-react-renderer": "^2.3.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0"
|
||||
"react-dom": "^19.0.0",
|
||||
"server-only": "^0.0.1",
|
||||
"tailwind-merge": "^3.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.10.2",
|
||||
"@docusaurus/tsconfig": "3.10.2",
|
||||
"@docusaurus/types": "3.10.2",
|
||||
"@types/react": "^19.0.0",
|
||||
"autoprefixer": "^10.5.3",
|
||||
"postcss": "^8.5.19",
|
||||
"tailwindcss": "^4.3.2",
|
||||
"typescript": "~6.0.2"
|
||||
},
|
||||
"browserslist": {
|
||||
|
||||
Generated
+432
-1
@@ -17,14 +17,23 @@ importers:
|
||||
'@docusaurus/preset-classic':
|
||||
specifier: 3.10.2
|
||||
version: 3.10.2(@algolia/client-search@5.56.0)(@docusaurus/faster@3.10.2(@docusaurus/types@3.10.2(@swc/core@1.15.43)(postcss@8.5.19)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(postcss@8.5.19))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.12)(@swc/core@1.15.43)(@types/react@19.2.17)(postcss@8.5.19)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(search-insights@2.17.3)(typescript@6.0.3)
|
||||
'@docusaurus/theme-common':
|
||||
specifier: ^3.10.2
|
||||
version: 3.10.2(@docusaurus/plugin-content-docs@3.10.2(@docusaurus/faster@3.10.2(@docusaurus/types@3.10.2(@swc/core@1.15.43)(postcss@8.5.19)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(postcss@8.5.19))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.12)(@swc/core@1.15.43)(postcss@8.5.19)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@swc/core@1.15.43)(postcss@8.5.19)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@docusaurus/theme-mermaid':
|
||||
specifier: ^3.10.2
|
||||
version: 3.10.2(@docusaurus/faster@3.10.2(@docusaurus/types@3.10.2(@swc/core@1.15.43)(postcss@8.5.19)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(postcss@8.5.19))(@docusaurus/plugin-content-docs@3.10.2(@docusaurus/faster@3.10.2(@docusaurus/types@3.10.2(@swc/core@1.15.43)(postcss@8.5.19)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(postcss@8.5.19))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.12)(@swc/core@1.15.43)(postcss@8.5.19)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3))(@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7))(@rspack/core@1.7.12)(@swc/core@1.15.43)(postcss@8.5.19)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(typescript@6.0.3)
|
||||
'@mdx-js/react':
|
||||
specifier: ^3.0.0
|
||||
version: 3.1.1(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-tooltip':
|
||||
specifier: ^1.2.12
|
||||
version: 1.2.12(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
clsx:
|
||||
specifier: ^2.0.0
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
docusaurus-plugin-drawio:
|
||||
specifier: ^0.4.0
|
||||
@@ -41,6 +50,12 @@ importers:
|
||||
react-dom:
|
||||
specifier: ^19.0.0
|
||||
version: 19.2.7(react@19.2.7)
|
||||
server-only:
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1
|
||||
tailwind-merge:
|
||||
specifier: ^3.6.0
|
||||
version: 3.6.0
|
||||
devDependencies:
|
||||
'@docusaurus/module-type-aliases':
|
||||
specifier: 3.10.2
|
||||
@@ -54,6 +69,15 @@ importers:
|
||||
'@types/react':
|
||||
specifier: ^19.0.0
|
||||
version: 19.2.17
|
||||
autoprefixer:
|
||||
specifier: ^10.5.3
|
||||
version: 10.5.3(postcss@8.5.19)
|
||||
postcss:
|
||||
specifier: ^8.5.19
|
||||
version: 8.5.19
|
||||
tailwindcss:
|
||||
specifier: ^4.3.2
|
||||
version: 4.3.2
|
||||
typescript:
|
||||
specifier: ~6.0.2
|
||||
version: 6.0.3
|
||||
@@ -1274,6 +1298,21 @@ packages:
|
||||
'@emnapi/wasi-threads@1.2.2':
|
||||
resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==}
|
||||
|
||||
'@floating-ui/core@1.8.0':
|
||||
resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==}
|
||||
|
||||
'@floating-ui/dom@1.8.0':
|
||||
resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==}
|
||||
|
||||
'@floating-ui/react-dom@2.1.9':
|
||||
resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
|
||||
'@floating-ui/utils@0.2.12':
|
||||
resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==}
|
||||
|
||||
'@hapi/hoek@9.3.0':
|
||||
resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
|
||||
|
||||
@@ -1537,6 +1576,206 @@ packages:
|
||||
'@polka/url@1.0.0-next.29':
|
||||
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
|
||||
|
||||
'@radix-ui/primitive@1.1.5':
|
||||
resolution: {integrity: sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==}
|
||||
|
||||
'@radix-ui/react-arrow@1.1.11':
|
||||
resolution: {integrity: sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.3':
|
||||
resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-context@1.2.0':
|
||||
resolution: {integrity: sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.15':
|
||||
resolution: {integrity: sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-id@1.1.2':
|
||||
resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-popper@1.3.3':
|
||||
resolution: {integrity: sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-portal@1.1.13':
|
||||
resolution: {integrity: sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-presence@1.1.7':
|
||||
resolution: {integrity: sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-primitive@2.1.7':
|
||||
resolution: {integrity: sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-slot@1.3.0':
|
||||
resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.12':
|
||||
resolution: {integrity: sha512-U3HoftgWnmla78vzQbLvKKb7bUYJxoiiqYFzp1wu/TBMyDqMZSuCl3aRICsD6EfVEwcJD2mumGDGUXLFVqQHKA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.2':
|
||||
resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-controllable-state@1.2.3':
|
||||
resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-effect-event@0.0.3':
|
||||
resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.2':
|
||||
resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-rect@1.1.2':
|
||||
resolution: {integrity: sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-use-size@1.1.2':
|
||||
resolution: {integrity: sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-visually-hidden@1.2.7':
|
||||
resolution: {integrity: sha512-1wNZBggTDK3GRuuQ6nP4k2yi7a6l7I5qbMPbZcRsrGsGVead/f/d5FhEzUvqFs0bcrDLx7n1zKQ3JvLR6whaaw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/rect@1.1.2':
|
||||
resolution: {integrity: sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==}
|
||||
|
||||
'@rspack/binding-darwin-arm64@1.7.12':
|
||||
resolution: {integrity: sha512-rbFprJaJiqrmfy8SHth8EsoRS0wg4bXcucwj9NiMzpGFq14Opw8c04iQ6H9BECYzgmN0PKZ9rh41LdVvhdZe4A==}
|
||||
cpu: [arm64]
|
||||
@@ -5276,6 +5515,9 @@ packages:
|
||||
resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
server-only@0.0.1:
|
||||
resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
@@ -5481,6 +5723,12 @@ packages:
|
||||
'@swc/core': ^1.2.147
|
||||
webpack: '>=2'
|
||||
|
||||
tailwind-merge@3.6.0:
|
||||
resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==}
|
||||
|
||||
tailwindcss@4.3.2:
|
||||
resolution: {integrity: sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==}
|
||||
|
||||
tapable@2.3.3:
|
||||
resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -8234,6 +8482,23 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@floating-ui/core@1.8.0':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.12
|
||||
|
||||
'@floating-ui/dom@1.8.0':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.8.0
|
||||
'@floating-ui/utils': 0.2.12
|
||||
|
||||
'@floating-ui/react-dom@2.1.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.8.0
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
|
||||
'@floating-ui/utils@0.2.12': {}
|
||||
|
||||
'@hapi/hoek@9.3.0': {}
|
||||
|
||||
'@hapi/topo@5.1.0':
|
||||
@@ -8609,6 +8874,166 @@ snapshots:
|
||||
|
||||
'@polka/url@1.0.0-next.29': {}
|
||||
|
||||
'@radix-ui/primitive@1.1.5': {}
|
||||
|
||||
'@radix-ui/react-arrow@1.1.11(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.7(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.17)(react@19.2.7)':
|
||||
dependencies:
|
||||
react: 19.2.7
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-context@1.2.0(@types/react@19.2.17)(react@19.2.7)':
|
||||
dependencies:
|
||||
react: 19.2.7
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-dismissable-layer@1.1.15(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.5
|
||||
'@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-primitive': 2.1.7(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7)
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-id@1.1.2(@types/react@19.2.17)(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
react: 19.2.7
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-popper@1.3.3(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@floating-ui/react-dom': 2.1.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-arrow': 1.1.11(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-primitive': 2.1.7(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-use-rect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-use-size': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/rect': 1.1.2
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-portal@1.1.13(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.7(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-presence@1.1.7(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-primitive@2.1.7(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7)
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-slot@1.3.0(@types/react@19.2.17)(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
|
||||
react: 19.2.7
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-tooltip@1.2.12(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.5
|
||||
'@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-dismissable-layer': 1.1.15(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-popper': 1.3.3(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-portal': 1.1.13(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-presence': 1.1.7(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-primitive': 2.1.7(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
'@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-visually-hidden': 1.2.7(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.17)(react@19.2.7)':
|
||||
dependencies:
|
||||
react: 19.2.7
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-controllable-state@1.2.3(@types/react@19.2.17)(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7)
|
||||
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
react: 19.2.7
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.17)(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
react: 19.2.7
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.17)(react@19.2.7)':
|
||||
dependencies:
|
||||
react: 19.2.7
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-rect@1.1.2(@types/react@19.2.17)(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/rect': 1.1.2
|
||||
react: 19.2.7
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-use-size@1.1.2(@types/react@19.2.17)(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7)
|
||||
react: 19.2.7
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/react-visually-hidden@1.2.7(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)':
|
||||
dependencies:
|
||||
'@radix-ui/react-primitive': 2.1.7(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
|
||||
react: 19.2.7
|
||||
react-dom: 19.2.7(react@19.2.7)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.17
|
||||
|
||||
'@radix-ui/rect@1.1.2': {}
|
||||
|
||||
'@rspack/binding-darwin-arm64@1.7.12':
|
||||
optional: true
|
||||
|
||||
@@ -12910,6 +13335,8 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
server-only@0.0.1: {}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
dependencies:
|
||||
define-data-property: 1.1.4
|
||||
@@ -13134,6 +13561,10 @@ snapshots:
|
||||
'@swc/counter': 0.1.3
|
||||
webpack: 5.108.4(@swc/core@1.15.43)(postcss@8.5.19)
|
||||
|
||||
tailwind-merge@3.6.0: {}
|
||||
|
||||
tailwindcss@4.3.2: {}
|
||||
|
||||
tapable@2.3.3: {}
|
||||
|
||||
terser-webpack-plugin@5.6.1(@swc/core@1.15.43)(clean-css@5.3.3)(cssnano@6.1.2(postcss@8.5.19))(html-minifier-terser@7.2.0)(postcss@8.5.19)(webpack@5.108.4(@swc/core@1.15.43)(postcss@8.5.19)):
|
||||
|
||||
+40
-15
@@ -2,50 +2,75 @@ import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';
|
||||
|
||||
const sidebars: SidebarsConfig = {
|
||||
docsSidebar: [
|
||||
/* ── Introduction ────────────────────────────────────── */
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'intro/index',
|
||||
label: '← Overview',
|
||||
label: 'Introduction',
|
||||
},
|
||||
|
||||
/* ── Platform ────────────────────────────────────────── */
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Applications',
|
||||
label: 'Platform',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'platform/overview',
|
||||
'platform/cicd',
|
||||
],
|
||||
},
|
||||
|
||||
/* ── Products ────────────────────────────────────────── */
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Products',
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
type: 'category',
|
||||
label: '📈 Stock Market Pro',
|
||||
link: { type: 'doc', id: 'applications/stock-market-professional/index' },
|
||||
label: 'Stock Market Pro',
|
||||
link: { type: 'doc', id: 'products/stock-market-pro/index' },
|
||||
items: [
|
||||
{
|
||||
type: 'category',
|
||||
label: 'C4 Architecture',
|
||||
collapsed: false,
|
||||
label: 'Architecture (C4)',
|
||||
items: [
|
||||
'applications/stock-market-professional/c4/context',
|
||||
'applications/stock-market-professional/c4/containers',
|
||||
'applications/stock-market-professional/c4/components',
|
||||
'products/stock-market-pro/c4/context',
|
||||
'products/stock-market-pro/c4/containers',
|
||||
'products/stock-market-pro/c4/components',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Modules',
|
||||
items: [
|
||||
'applications/stock-market-professional/data-monitoring/index',
|
||||
'applications/stock-market-professional/technical-analysis/index',
|
||||
'applications/stock-market-professional/signal-generation/index',
|
||||
'products/stock-market-pro/data-monitoring/index',
|
||||
'products/stock-market-pro/technical-analysis/index',
|
||||
'products/stock-market-pro/signal-generation/index',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/* ── Engineering ─────────────────────────────────────── */
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Platform',
|
||||
label: 'Engineering',
|
||||
collapsed: false,
|
||||
items: [
|
||||
'architecture/overview',
|
||||
'guides/getting-started',
|
||||
'engineering/guidelines',
|
||||
],
|
||||
},
|
||||
|
||||
/* ── Runbooks ────────────────────────────────────────── */
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Runbooks',
|
||||
collapsed: true,
|
||||
items: [
|
||||
'runbooks/index',
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '@site/src/lib/utils';
|
||||
|
||||
/* ── Badge ───────────────────────────────────────────────── */
|
||||
type BadgeVariant = 'default' | 'secondary' | 'outline' | 'destructive' | 'success' | 'warning' | 'wip' | 'stable' | 'planned';
|
||||
|
||||
interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
||||
variant?: BadgeVariant;
|
||||
}
|
||||
|
||||
const badgeVariants: Record<BadgeVariant, string> = {
|
||||
default: 'bg-primary text-primary-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground',
|
||||
outline: 'border border-border text-foreground',
|
||||
destructive: 'bg-destructive text-destructive-foreground',
|
||||
success: 'border border-emerald-500 text-emerald-500 bg-emerald-500/8',
|
||||
warning: 'border border-amber-500 text-amber-500 bg-amber-500/8',
|
||||
wip: 'border border-amber-500 text-amber-500 bg-amber-500/8',
|
||||
stable: 'border border-emerald-500 text-emerald-500 bg-emerald-500/8',
|
||||
planned: 'border border-border text-muted-foreground',
|
||||
};
|
||||
|
||||
export function Badge({ variant = 'default', className, children, ...props }: BadgeProps) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1 text-[0.65rem] font-semibold uppercase tracking-widest px-2 py-0.5',
|
||||
badgeVariants[variant],
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── Card ────────────────────────────────────────────────── */
|
||||
export function Card({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn('border border-border bg-card text-card-foreground', className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardHeader({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn('flex flex-col gap-1.5 p-5', className)} {...props}>{children}</div>;
|
||||
}
|
||||
|
||||
export function CardTitle({ className, children, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
||||
return (
|
||||
<h3 className={cn('text-base font-semibold tracking-tight leading-none', className)} {...props}>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
|
||||
export function CardContent({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn('p-5 pt-0', className)} {...props}>{children}</div>;
|
||||
}
|
||||
|
||||
/* ── Callout ─────────────────────────────────────────────── */
|
||||
type CalloutVariant = 'info' | 'warning' | 'danger' | 'success' | 'note';
|
||||
|
||||
interface CalloutProps {
|
||||
variant?: CalloutVariant;
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const calloutStyles: Record<CalloutVariant, { border: string; bg: string; title: string; icon: string }> = {
|
||||
note: { border: 'border-border', bg: 'bg-muted/40', title: 'text-foreground', icon: '◆' },
|
||||
info: { border: 'border-indigo-500', bg: 'bg-indigo-500/8', title: 'text-indigo-400', icon: 'ℹ' },
|
||||
success: { border: 'border-emerald-500',bg: 'bg-emerald-500/8', title: 'text-emerald-400', icon: '✓' },
|
||||
warning: { border: 'border-amber-500', bg: 'bg-amber-500/8', title: 'text-amber-400', icon: '⚠' },
|
||||
danger: { border: 'border-red-500', bg: 'bg-red-500/8', title: 'text-red-400', icon: '✕' },
|
||||
};
|
||||
|
||||
export function Callout({ variant = 'note', title, children, className }: CalloutProps) {
|
||||
const s = calloutStyles[variant];
|
||||
return (
|
||||
<div className={cn('border-l-2 border px-4 py-3 my-4 text-sm', s.border, s.bg, className)}>
|
||||
{title && (
|
||||
<div className={cn('flex items-center gap-1.5 font-semibold uppercase tracking-widest text-[0.7rem] mb-2', s.title)}>
|
||||
<span>{s.icon}</span>
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-muted-foreground leading-relaxed">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── PropertiesTable ─────────────────────────────────────── */
|
||||
interface Property {
|
||||
name: string;
|
||||
type: string;
|
||||
default?: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface PropertiesTableProps {
|
||||
properties: Property[];
|
||||
}
|
||||
|
||||
export function PropertiesTable({ properties }: PropertiesTableProps) {
|
||||
return (
|
||||
<div className="not-prose my-4 border border-border overflow-hidden text-sm">
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr className="bg-muted border-b border-border">
|
||||
<th className="text-left px-4 py-2.5 font-semibold text-[0.7rem] uppercase tracking-wider text-muted-foreground w-36">Name</th>
|
||||
<th className="text-left px-4 py-2.5 font-semibold text-[0.7rem] uppercase tracking-wider text-muted-foreground w-36">Type</th>
|
||||
<th className="text-left px-4 py-2.5 font-semibold text-[0.7rem] uppercase tracking-wider text-muted-foreground w-24">Default</th>
|
||||
<th className="text-left px-4 py-2.5 font-semibold text-[0.7rem] uppercase tracking-wider text-muted-foreground">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{properties.map((p, i) => (
|
||||
<tr key={p.name} className={cn('border-b border-border last:border-0', i % 2 === 0 ? '' : 'bg-muted/30')}>
|
||||
<td className="px-4 py-2.5 font-mono text-xs text-indigo-400">{p.name}</td>
|
||||
<td className="px-4 py-2.5 font-mono text-xs text-amber-400">{p.type}</td>
|
||||
<td className="px-4 py-2.5 font-mono text-xs text-muted-foreground">{p.default ?? '—'}</td>
|
||||
<td className="px-4 py-2.5 text-muted-foreground text-xs leading-relaxed">{p.description}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ── StatusIndicator ─────────────────────────────────────── */
|
||||
type Status = 'operational' | 'degraded' | 'outage' | 'maintenance';
|
||||
|
||||
const statusConfig: Record<Status, { color: string; label: string }> = {
|
||||
operational: { color: 'bg-emerald-500', label: 'Operational' },
|
||||
degraded: { color: 'bg-amber-500', label: 'Degraded' },
|
||||
outage: { color: 'bg-red-500', label: 'Outage' },
|
||||
maintenance: { color: 'bg-indigo-500', label: 'Maintenance' },
|
||||
};
|
||||
|
||||
export function StatusIndicator({ status }: { status: Status }) {
|
||||
const { color, label } = statusConfig[status];
|
||||
return (
|
||||
<span className="inline-flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span className={cn('w-1.5 h-1.5 inline-block', color)} />
|
||||
{label}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
+175
-169
@@ -1,27 +1,74 @@
|
||||
/* ============================================================
|
||||
SKIC Playground Docs — Modern Dark-First Technical Theme
|
||||
Inspired by Vercel / Linear design language
|
||||
Font: Geist (by Vercel) + Geist Mono
|
||||
SKIC Playground Docs — Dark-first, Sharp, Technical Theme
|
||||
Stack: Docusaurus + Tailwind (preflight off) + shadcn/ui (New York / Zinc)
|
||||
Design: no rounded corners, Vercel/Linear aesthetic
|
||||
============================================================ */
|
||||
|
||||
/* --- Google Fonts / Geist import --- */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Geist via CDN */
|
||||
/* --- Fonts --- */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://cdn.jsdelivr.net/npm/geist@1.3.0/dist/fonts/geist-sans/style.css');
|
||||
@import url('https://cdn.jsdelivr.net/npm/geist@1.3.0/dist/fonts/geist-mono/style.css');
|
||||
|
||||
/* ── Design Tokens ───────────────────────────────────────── */
|
||||
:root {
|
||||
/* Brand */
|
||||
--skic-accent: #6366f1; /* indigo-500 */
|
||||
--skic-accent-light: #818cf8; /* indigo-400 */
|
||||
--skic-accent-dark: #4f46e5; /* indigo-600 */
|
||||
--skic-green: #10b981;
|
||||
--skic-amber: #f59e0b;
|
||||
--skic-red: #ef4444;
|
||||
/* ── shadcn/ui CSS variables (New York / Zinc, radius = 0) ── */
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5.9% 10%;
|
||||
--radius: 0rem; /* SHARP — no rounded corners */
|
||||
/* brand accent */
|
||||
--skic-accent: 239 84% 67%;
|
||||
--skic-green: 160 84% 39%;
|
||||
--skic-amber: 38 92% 50%;
|
||||
--skic-red: 0 84% 60%;
|
||||
}
|
||||
|
||||
/* Docusaurus overrides — Light mode */
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
--radius: 0rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Docusaurus / Infima token overrides ─────────────────── */
|
||||
:root {
|
||||
--ifm-color-primary: #6366f1;
|
||||
--ifm-color-primary-dark: #4f46e5;
|
||||
--ifm-color-primary-darker: #4338ca;
|
||||
@@ -34,7 +81,6 @@
|
||||
--ifm-font-family-monospace: 'Geist Mono', 'Fira Code', monospace;
|
||||
--ifm-font-size-base: 15px;
|
||||
--ifm-line-height-base: 1.7;
|
||||
--ifm-font-weight-normal: 400;
|
||||
|
||||
--ifm-heading-font-weight: 600;
|
||||
--ifm-heading-letter-spacing: -0.02em;
|
||||
@@ -42,25 +88,27 @@
|
||||
--ifm-h2-font-size: 1.5rem;
|
||||
--ifm-h3-font-size: 1.2rem;
|
||||
|
||||
--ifm-navbar-height: 60px;
|
||||
--ifm-navbar-height: 56px;
|
||||
--ifm-navbar-shadow: none;
|
||||
--ifm-navbar-background-color: #0a0a0a;
|
||||
|
||||
--ifm-toc-border-color: var(--ifm-color-emphasis-200);
|
||||
/* SHARP — zero radius everywhere */
|
||||
--ifm-global-radius: 0;
|
||||
--ifm-code-border-radius: 0;
|
||||
--ifm-pre-border-radius: 0;
|
||||
--ifm-alert-border-radius: 0;
|
||||
--ifm-card-border-radius: 0;
|
||||
--ifm-button-border-radius: 0;
|
||||
--ifm-pagination-nav-border-radius: 0;
|
||||
--ifm-badge-border-radius: 0;
|
||||
--ifm-toc-border-color: #1f1f1f;
|
||||
--ifm-code-font-size: 0.85rem;
|
||||
--ifm-code-border-radius: 6px;
|
||||
--ifm-code-padding-horizontal: 0.4em;
|
||||
--ifm-code-padding-vertical: 0.15em;
|
||||
--ifm-pre-border-radius: 10px;
|
||||
--ifm-pre-padding: 1.25rem;
|
||||
|
||||
--ifm-global-radius: 8px;
|
||||
--ifm-global-shadow-lw: 0 1px 3px 0 rgba(0,0,0,.08);
|
||||
--ifm-global-shadow-md: 0 4px 12px 0 rgba(0,0,0,.1);
|
||||
|
||||
--docusaurus-highlighted-code-line-bg: rgba(99, 102, 241, 0.08);
|
||||
}
|
||||
|
||||
/* ── Dark Mode ───────────────────────────────────────────── */
|
||||
/* Dark mode Infima overrides */
|
||||
[data-theme='dark'] {
|
||||
--ifm-color-primary: #818cf8;
|
||||
--ifm-color-primary-dark: #6366f1;
|
||||
@@ -72,86 +120,94 @@
|
||||
|
||||
--ifm-background-color: #0a0a0a;
|
||||
--ifm-background-surface-color: #111111;
|
||||
--ifm-navbar-background-color: rgba(10, 10, 10, 0.85);
|
||||
--ifm-navbar-background-color: #0a0a0a;
|
||||
--ifm-sidebar-background: #0a0a0a;
|
||||
--ifm-footer-background-color: #0a0a0a;
|
||||
|
||||
--ifm-color-content: #ededed;
|
||||
--ifm-color-content-secondary:#a1a1a1;
|
||||
--ifm-color-emphasis-100: #1a1a1a;
|
||||
--ifm-color-emphasis-200: #222222;
|
||||
--ifm-color-emphasis-300: #333333;
|
||||
--ifm-color-emphasis-400: #555555;
|
||||
--ifm-color-emphasis-600: #999999;
|
||||
--ifm-color-content-secondary: #a1a1a1;
|
||||
--ifm-color-emphasis-100: #161616;
|
||||
--ifm-color-emphasis-200: #1f1f1f;
|
||||
--ifm-color-emphasis-300: #2a2a2a;
|
||||
--ifm-color-emphasis-400: #444444;
|
||||
--ifm-color-emphasis-600: #888888;
|
||||
--ifm-color-emphasis-700: #cccccc;
|
||||
--ifm-color-emphasis-900: #ffffff;
|
||||
|
||||
--ifm-toc-border-color: #222222;
|
||||
--ifm-hr-border-color: #1f1f1f;
|
||||
|
||||
--ifm-code-background: #161616;
|
||||
--ifm-pre-background: #111111;
|
||||
--ifm-blockquote-border-left-color: var(--skic-accent);
|
||||
|
||||
--docusaurus-highlighted-code-line-bg: rgba(129, 140, 248, 0.12);
|
||||
}
|
||||
|
||||
/* ── Typography ──────────────────────────────────────────── */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
letter-spacing: -0.02em;
|
||||
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
|
||||
}
|
||||
|
||||
h1 { font-size: 2.25rem; font-weight: 700; }
|
||||
h2 { font-size: 1.5rem; border-bottom: 1px solid var(--ifm-color-emphasis-200); padding-bottom: 0.4rem; }
|
||||
h3 { font-size: 1.15rem; }
|
||||
|
||||
p { color: var(--ifm-color-content); }
|
||||
|
||||
/* ── Navbar ──────────────────────────────────────────────── */
|
||||
/* ── Force dark navbar in BOTH modes ─────────────────────── */
|
||||
/* This is the key fix — lock navbar dark regardless of color scheme */
|
||||
.navbar {
|
||||
border-bottom: 1px solid var(--ifm-color-emphasis-200);
|
||||
background-color: #0a0a0a !important;
|
||||
border-bottom: 1px solid #1f1f1f !important;
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
[data-theme='dark'] .navbar {
|
||||
background: rgba(10, 10, 10, 0.85);
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.navbar__title {
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
font-size: 0.95rem;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.navbar__logo {
|
||||
height: 24px;
|
||||
color: #ededed !important;
|
||||
}
|
||||
|
||||
.navbar__link {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: var(--ifm-color-content-secondary);
|
||||
transition: color 0.15s ease;
|
||||
color: #a1a1a1 !important;
|
||||
transition: color 0.12s ease;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.navbar__link:hover,
|
||||
.navbar__link--active {
|
||||
color: var(--ifm-color-content) !important;
|
||||
color: #ededed !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.navbar__logo { height: 22px; }
|
||||
|
||||
.navbar-sidebar {
|
||||
background-color: #0a0a0a !important;
|
||||
}
|
||||
|
||||
.navbar-sidebar__brand {
|
||||
background-color: #0a0a0a !important;
|
||||
border-bottom: 1px solid #1f1f1f !important;
|
||||
}
|
||||
|
||||
/* ── Typography ──────────────────────────────────────────── */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
border-bottom: 1px solid var(--ifm-color-emphasis-200);
|
||||
padding-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
/* ── Sidebar ─────────────────────────────────────────────── */
|
||||
.theme-doc-sidebar-container {
|
||||
border-right: 1px solid var(--ifm-color-emphasis-200) !important;
|
||||
border-right: 1px solid #1f1f1f !important;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .theme-doc-sidebar-container {
|
||||
background-color: #0a0a0a;
|
||||
}
|
||||
|
||||
.menu__link {
|
||||
font-size: 0.875rem;
|
||||
border-radius: 6px;
|
||||
padding: 0.35rem 0.75rem;
|
||||
border-radius: 0 !important;
|
||||
padding: 0.3rem 0.75rem;
|
||||
color: var(--ifm-color-content-secondary);
|
||||
transition: all 0.15s ease;
|
||||
transition: all 0.12s ease;
|
||||
}
|
||||
|
||||
.menu__link:hover {
|
||||
@@ -163,40 +219,43 @@ p { color: var(--ifm-color-content); }
|
||||
background: var(--ifm-color-emphasis-100) !important;
|
||||
color: var(--ifm-color-primary) !important;
|
||||
font-weight: 600;
|
||||
border-left: 2px solid var(--ifm-color-primary);
|
||||
}
|
||||
|
||||
.menu__caret::before {
|
||||
background: var(--ifm-color-content-secondary);
|
||||
}
|
||||
.menu__caret { border-radius: 0 !important; }
|
||||
|
||||
/* ── Code Blocks ─────────────────────────────────────────── */
|
||||
/* ── Code blocks ─────────────────────────────────────────── */
|
||||
code {
|
||||
font-size: 0.84em;
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
border-radius: 5px;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.prism-code {
|
||||
font-size: 0.84rem;
|
||||
line-height: 1.6;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.codeBlockContainer_node_modules-\@docusaurus-theme-classic-lib-theme-CodeBlock-Container-styles-module {
|
||||
div[class*='codeBlockContainer'] {
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
box-shadow: none !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
div[class*="codeBlockContainer"] {
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
div[class*="codeBlockTitle"] {
|
||||
div[class*='codeBlockTitle'] {
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
border-bottom: 1px solid var(--ifm-color-emphasis-200);
|
||||
font-size: 0.78rem;
|
||||
font-family: var(--ifm-font-family-monospace);
|
||||
color: var(--ifm-color-content-secondary);
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
div[class*='copyButton'],
|
||||
div[class*='codeButton'] {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* ── Tables ──────────────────────────────────────────────── */
|
||||
@@ -205,55 +264,45 @@ table {
|
||||
width: 100%;
|
||||
font-size: 0.875rem;
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
border-radius: 8px;
|
||||
border-radius: 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
thead {
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
}
|
||||
thead { background: var(--ifm-color-emphasis-100); }
|
||||
|
||||
th {
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
letter-spacing: 0.07em;
|
||||
color: var(--ifm-color-content-secondary);
|
||||
padding: 0.75rem 1rem;
|
||||
padding: 0.65rem 1rem;
|
||||
border-bottom: 1px solid var(--ifm-color-emphasis-200);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 0.7rem 1rem;
|
||||
padding: 0.65rem 1rem;
|
||||
border-bottom: 1px solid var(--ifm-color-emphasis-100);
|
||||
}
|
||||
|
||||
tr:last-child td { border-bottom: none; }
|
||||
|
||||
tbody tr:hover {
|
||||
background: var(--ifm-color-emphasis-100);
|
||||
}
|
||||
tbody tr:hover { background: var(--ifm-color-emphasis-100); }
|
||||
|
||||
/* ── Admonitions (callouts) ──────────────────────────────── */
|
||||
/* ── Admonitions ─────────────────────────────────────────── */
|
||||
.admonition {
|
||||
border-radius: 8px;
|
||||
border-radius: 0 !important;
|
||||
border: 1px solid;
|
||||
border-left-width: 3px;
|
||||
border-left-width: 2px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.admonition-note { border-color: var(--ifm-color-emphasis-300); background: var(--ifm-color-emphasis-100); }
|
||||
.admonition-tip { border-color: var(--skic-green); background: rgba(16, 185, 129, 0.06); }
|
||||
.admonition-info { border-color: var(--skic-accent); background: rgba(99, 102, 241, 0.06); }
|
||||
.admonition-warning { border-color: var(--skic-amber); background: rgba(245, 158, 11, 0.06); }
|
||||
.admonition-danger { border-color: var(--skic-red); background: rgba(239, 68, 68, 0.06); }
|
||||
|
||||
.admonition-heading h5 { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.06em; }
|
||||
|
||||
/* ── Breadcrumbs ─────────────────────────────────────────── */
|
||||
.breadcrumbs__link {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.admonition-tip { border-color: #10b981; background: rgba(16,185,129,0.05); }
|
||||
.admonition-info { border-color: #6366f1; background: rgba(99,102,241,0.05); }
|
||||
.admonition-warning { border-color: #f59e0b; background: rgba(245,158,11,0.05); }
|
||||
.admonition-danger { border-color: #ef4444; background: rgba(239,68,68,0.05); }
|
||||
.admonition-heading h5 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.07em; }
|
||||
|
||||
/* ── TOC ─────────────────────────────────────────────────── */
|
||||
.table-of-contents__link {
|
||||
@@ -261,15 +310,13 @@ tbody tr:hover {
|
||||
color: var(--ifm-color-content-secondary);
|
||||
}
|
||||
.table-of-contents__link:hover,
|
||||
.table-of-contents__link--active {
|
||||
color: var(--ifm-color-primary);
|
||||
}
|
||||
.table-of-contents__link--active { color: var(--ifm-color-primary); }
|
||||
|
||||
/* ── Pagination ──────────────────────────────────────────── */
|
||||
.pagination-nav__link {
|
||||
border: 1px solid var(--ifm-color-emphasis-200);
|
||||
border-radius: 8px;
|
||||
transition: all 0.15s ease;
|
||||
border-radius: 0 !important;
|
||||
transition: all 0.12s ease;
|
||||
}
|
||||
.pagination-nav__link:hover {
|
||||
border-color: var(--ifm-color-primary);
|
||||
@@ -278,12 +325,12 @@ tbody tr:hover {
|
||||
|
||||
/* ── Footer ──────────────────────────────────────────────── */
|
||||
.footer {
|
||||
border-top: 1px solid var(--ifm-color-emphasis-200);
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid #1f1f1f;
|
||||
background-color: #0a0a0a !important;
|
||||
}
|
||||
|
||||
.footer__title {
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.72rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
font-weight: 600;
|
||||
@@ -295,66 +342,25 @@ tbody tr:hover {
|
||||
color: var(--ifm-color-content-secondary);
|
||||
}
|
||||
|
||||
/* ── Doc article ─────────────────────────────────────────── */
|
||||
.markdown {
|
||||
--ifm-heading-margin-top: 1.75rem;
|
||||
/* ── Buttons ─────────────────────────────────────────────── */
|
||||
.button {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* ── Doc content ─────────────────────────────────────────── */
|
||||
.markdown { --ifm-heading-margin-top: 1.75rem; }
|
||||
.markdown > h2:first-child { margin-top: 0; }
|
||||
|
||||
/* ── Hero (homepage) ─────────────────────────────────────── */
|
||||
.hero--primary {
|
||||
background: linear-gradient(135deg, #0a0a0a 0%, #111827 50%, #0a0a0a 100%);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero--primary::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: radial-gradient(ellipse at 60% 40%, rgba(99,102,241,0.15) 0%, transparent 65%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hero__title {
|
||||
font-size: 3rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.04em;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #a5b4fc 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.hero__subtitle {
|
||||
color: #a1a1a1;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ── Badge component ─────────────────────────────────────── */
|
||||
.badge--status {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
padding: 0.2rem 0.6rem;
|
||||
border-radius: 100px;
|
||||
border: 1px solid;
|
||||
}
|
||||
.badge--wip { color: var(--skic-amber); border-color: var(--skic-amber); background: rgba(245,158,11,0.08); }
|
||||
.badge--stable { color: var(--skic-green); border-color: var(--skic-green); background: rgba(16,185,129,0.08); }
|
||||
.badge--planned { color: var(--ifm-color-content-secondary); border-color: var(--ifm-color-emphasis-300); background: transparent; }
|
||||
|
||||
/* ── Scrollbar (webkit) ──────────────────────────────────── */
|
||||
[data-theme='dark'] ::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
[data-theme='dark'] ::-webkit-scrollbar-track { background: transparent; }
|
||||
[data-theme='dark'] ::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
|
||||
[data-theme='dark'] ::-webkit-scrollbar-thumb:hover { background: #555; }
|
||||
/* ── Scrollbar ───────────────────────────────────────────── */
|
||||
::-webkit-scrollbar { width: 5px; height: 5px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: #2a2a2a; }
|
||||
::-webkit-scrollbar-thumb:hover { background: #444; }
|
||||
|
||||
/* ── Selection ───────────────────────────────────────────── */
|
||||
::selection { background: rgba(99,102,241,0.25); }
|
||||
|
||||
/* ── Announcement bar ────────────────────────────────────── */
|
||||
div[class*='announcementBar'] {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
+1
-1
@@ -20,7 +20,7 @@ const applications: Application[] = [
|
||||
name: 'Stock Market Professional',
|
||||
description: 'Monitors forex & stock markets, runs technical analysis to spot patterns, and emits daily trading signals with max 3 positions / 30-minute hold.',
|
||||
status: 'wip',
|
||||
link: '/docs/applications/stock-market-professional',
|
||||
link: '/docs/products/stock-market-pro',
|
||||
tags: ['Python', 'Forex', 'TA', 'Signals'],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import Layout from '@theme-original/Layout';
|
||||
import type { Props } from '@theme/Layout';
|
||||
import { useColorMode } from '@docusaurus/theme-common';
|
||||
|
||||
/**
|
||||
* ThemeSynchronizer: bridges Docusaurus's data-theme="dark" attribute
|
||||
* with shadcn/ui's required `.dark` CSS class on <html>.
|
||||
*/
|
||||
function ThemeSynchronizer(): null {
|
||||
const { colorMode } = useColorMode();
|
||||
useEffect(() => {
|
||||
document.documentElement.classList.toggle('dark', colorMode === 'dark');
|
||||
}, [colorMode]);
|
||||
return null;
|
||||
}
|
||||
|
||||
export default function LayoutWrapper(props: Props): React.JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Layout {...props}>
|
||||
<ThemeSynchronizer />
|
||||
{props.children}
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./src/**/*.{js,jsx,ts,tsx,mdx}',
|
||||
'./docs/**/*.{md,mdx}',
|
||||
],
|
||||
darkMode: ['class'], // ThemeSynchronizer bridges Docusaurus data-theme → .dark class
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Geist', 'Inter', 'system-ui', 'sans-serif'],
|
||||
mono: ['Geist Mono', 'Fira Code', 'monospace'],
|
||||
},
|
||||
colors: {
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: 'hsl(var(--secondary))',
|
||||
foreground: 'hsl(var(--secondary-foreground))',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: 'hsl(var(--muted))',
|
||||
foreground: 'hsl(var(--muted-foreground))',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: 'hsl(var(--accent))',
|
||||
foreground: 'hsl(var(--accent-foreground))',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: 'hsl(var(--destructive))',
|
||||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
// All set to 0 — Lego wants sharp corners everywhere
|
||||
lg: '0px',
|
||||
md: '0px',
|
||||
sm: '0px',
|
||||
none: '0px',
|
||||
},
|
||||
},
|
||||
},
|
||||
corePlugins: {
|
||||
preflight: false, // MUST be false — prevents fighting Docusaurus Infima CSS
|
||||
},
|
||||
};
|
||||
+2
-1
@@ -6,7 +6,8 @@
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"ignoreDeprecations": "6.0",
|
||||
"strict": true
|
||||
"strict": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"exclude": [".docusaurus", "build"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user