diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..afdf218 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,35 @@ +name: Deploy landing page + +# Publishes site/ to GitHub Pages whenever the page changes on main. +on: + push: + branches: [main] + paths: + - "site/**" + - ".github/workflows/pages.yml" + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# One deploy at a time; let a newer push supersede an in-flight run. +concurrency: + group: pages + cancel-in-progress: true + +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v4 + - uses: actions/configure-pages@v5 + - uses: actions/upload-pages-artifact@v3 + with: + path: site + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 856f464..be8d71f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ globalTypes.d.luau # Editor / OS .DS_Store .vscode/ + +# Local Claude Code preview/launch config +.claude/ diff --git a/README.md b/README.md index f1f1017..0cd746b 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,118 @@
+SurvivorCore + # SurvivorCore -**A batteries-included, creator-extensible survival game framework for Roblox.** +**A batteries-included, creator-extensible survival game engine for Roblox.** + +[![Release](https://img.shields.io/github/v/release/TemujinCalidius/SurvivorCore?include_prereleases&color=5ACD78)](https://github.com/TemujinCalidius/SurvivorCore/releases) +[![CI](https://github.com/TemujinCalidius/SurvivorCore/actions/workflows/ci.yml/badge.svg)](https://github.com/TemujinCalidius/SurvivorCore/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) + +**[🌐 Website](https://temujincalidius.github.io/SurvivorCore/)** Β· **[πŸ“– Docs](docs/)** Β· **[πŸ“Ή Demo](https://makertube.net/w/tyn8JEMG3CaMbTXid8osdU)** Β· **[⬇ Releases](https://github.com/TemujinCalidius/SurvivorCore/releases)**
-> ⚠️ **Status: early scaffold (v0.1.0).** The foundation layer and architecture are in -> place; survival systems are being extracted from a production game ([The Counter -> Earth](https://github.com/TemujinCalidius/TheCounterEarth)) into this engine. APIs will -> change. Not yet production-ready. +> **Status: v0.5.0 β€” pre-release.** The core survival loop is in and working; the engine is +> being grown toward v1.0 and APIs may still shift. Production-tested in +> [The Counter Earth](https://thecounterearth.com). ## What it is -SurvivorCore gives you the *mechanics* of a survival game β€” stats (energy/hunger/thirst…), -inventory, crafting, cooking, harvesting, hostile mobs & combat, achievements, a codex, and -player persistence β€” without dictating your *content*. You bring your own items, world, and -art; the engine wires up the behavior. +SurvivorCore gives you the *mechanics* of a survival game β€” without dictating your *content*. +You bring the items, world and art; the engine wires up the behavior, the UI, and the no-code +authoring tools. If you know Roblox Studio, you can build a survival game. -## See it live +- **Survival stats & HUD** β€” health, energy, hunger, thirst, fatigue, blood, poison; ticking, + draining, and biting back, on a clean restyleable HUD. +- **Inventory, hotbar & crafting** β€” slots + carry-weight, a 9-slot hotbar, equipment, and hand + crafting in a tabbed, drag-and-drop menu β€” server-authoritative. +- **Gathering** β€” tag any mesh `Gatherable`: hold-E or tool-swing, per-hit yields, and reaction + hooks (a reed sways; a tree fells and leaves a stump). +- **Combat β€” melee & bow** β€” click to swing, or aim a bow and loose arrows that arc under real + weight. One server-validated `combat:hit` / `combat:kill` schema. +- **Mobs & AI** β€” a shared FSM substrate: hostile mobs chase & attack, passive animals flee. + Behavior is data β€” a mob's `faction` picks the profile. +- **No-code admin plugin** β€” create items, weapons, ammo and mobs from a Studio form (damage, + range, arrow curve, weight, aggro, leash) and drop them into the world. No scripting. -SurvivorCore powers **[The Counter Earth](https://thecounterearth.com)** β€” a full survival -game built on this engine, and its live showcase. The Counter Earth is in **closed Alpha** -(not yet open to beta testers), but SurvivorCore contributors and engine devs may get early -access β€” a good reason to [get involved](CONTRIBUTING.md). πŸ‘€ +Everything is **content-free by design**, **server-authoritative**, and **restyleable** β€” the UI +is real Instances driven by attributes, and every default is overridable. -## Two ways to extend it - -**1. Programmatic β€” register content from code:** +## Quickstart ```lua local SurvivorCore = require(ReplicatedStorage.SurvivorCore) -SurvivorCore.Items.register({ id = "reed", name = "Reed", stack = 20 }) -SurvivorCore.Recipes.register({ - id = "reed_basket", station = "hand", - ingredients = { { item = "reed", count = 5 } }, - output = { item = "reed_basket", count = 1 }, -}) +-- register content from code (or author it no-code in the admin plugin) +SurvivorCore.Items.register({ id = "berry", name = "Wild Berries", stack = 20 }) +SurvivorCore.Mobs.register({ id = "husk", faction = "hostile", health = 60 }) -SurvivorCore.start() +SurvivorCore.start() -- boots stats, inventory, gathering, crafting, combat, mobs… ``` -**2. Creator-owned β€” attach behavior to your own objects:** +The built-in HUD loader calls `SurvivorCore.startClient()` for you. -Build *any* mesh, tag it `Gatherable`, and set attributes β€” no engine-side definition needed: +**Install** β€” drop `SurvivorCore.rbxm` from a [Release](https://github.com/TemujinCalidius/SurvivorCore/releases) +into `ReplicatedStorage`, or add it via [Wally](https://wally.run): -| Attribute | Meaning | -|---|---| -| `ItemId` | what it yields | -| `Yield` | amount per full harvest | -| `HP` | hits to deplete | +```toml +# wally.toml +[dependencies] +SurvivorCore = "temujincalidius/survivorcore@0.5.0" +``` -The same pattern extends to craftables, huntables, farmables, and more. For advanced, -game-specific behavior (e.g. trees that physically fall and cut into pieces), hook into -engine lifecycle events instead of forking the engine: +Working from source? Clone and `rojo serve` the `demo.project.json` place. + +## Creator-owned, no code + +Build *any* mesh, tag it with a component, set attributes β€” no engine-side definition needed: + +| Tag | Set | It becomes | +|---|---|---| +| `Gatherable` | `Resource = "oak_tree"` | a harvestable node (item/HP/tool/yield from the def) | +| `Mob` | `MobType = "husk"` | a creature that runs the AI FSM | + +For game-specific flourish (a tree that physically fells, a custom death effect), hook the engine +lifecycle instead of forking it: ```lua -SurvivorCore.Hooks.on("gather:depleted", function(ctx) - -- your custom drop / VFX / physics here -end) +SurvivorCore.Gather.onReaction("oak_tree", "depleted", function(ctx) fellTrunk(ctx.instance) end) +SurvivorCore.Hooks.on("combat:kill", function(ctx) awardXp(ctx.attacker) end) ``` -## Getting started +The **admin plugin** turns all of this into Studio forms β€” see +[docs/admin-plugin.md](docs/admin-plugin.md) and [docs/content-authoring.md](docs/content-authoring.md). -- **Developers** β€” consume via [Rojo](https://rojo.space) (clone + `rojo serve`) or, once - published, as a [Wally](https://wally.run) package. -- **Drop-in** β€” grab `SurvivorCore.rbxm` from a [Release](../../releases) (auto-built from - source via `rojo build`), drop it into `ReplicatedStorage`, and add a short bootstrap - script that `require`s it and calls `.start()`. -- **Tuning, no code** β€” owners can adjust the survival stats from a Studio form with the - [Survival Stats admin plugin](docs/admin-plugin.md). It's editor tooling, so it installs - separately from the engine (built once into your Studio plugins folder). +## Docs + +[Architecture](docs/architecture.md) Β· [Survival stats + HUD](docs/survival-stats.md) Β· +[Inventory](docs/inventory.md) Β· [Harvesting](docs/harvesting.md) Β· [Crafting](docs/crafting.md) Β· +[Combat](docs/combat.md) Β· [Mobs & AI](docs/mobs.md) Β· [No-code content](docs/content-authoring.md) Β· +[Admin plugin](docs/admin-plugin.md) Β· [Design language](docs/design-language.md) Β· +[Extending](docs/extending.md) ## Project layout ``` src/ - init.luau -- SurvivorCore root: .start() + the public API - foundation/ -- Config, Assets, EventBridge, Hooks, Registry (the core plumbing) - registries/ -- Items, Recipes, Stats, Achievements, Codex, Appearance, Mobs - components/ -- creator-facing tag/attribute components (e.g. Gatherable) -demo/ -- a runnable demo place that consumes the engine -docs/architecture.md -- how the layers fit together + init.luau -- SurvivorCore root: .start() / .startClient() + the public API + foundation/ -- Config, Assets, EventBridge, Hooks, Reactions, Registry + registries/ -- Items, Recipes, Resources, Stats, Mobs, … + components/ -- creator-facing tag/attribute components (Gatherable, Mob) + systems/ -- Inventory, Harvesting, Crafting, Combat, Mobs, SurvivalStats… + client/ -- HUD + UI binders, combat/harvest input +plugin/ -- the Studio admin plugin (separate Rojo tree) +demo/ -- a runnable demo place that consumes the engine +site/ -- the landing page (deployed to GitHub Pages) +docs/ -- documentation ``` ## Sponsor -SurvivorCore is free and open source. If it's useful to you β€” or you just want to help the -engine grow β€” you can support development via -[GitHub Sponsors](https://github.com/sponsors/TemujinCalidius). πŸ’› +SurvivorCore is free and open source. If it's useful β€” or you just want to help the engine grow β€” +support development via [GitHub Sponsors](https://github.com/sponsors/TemujinCalidius). πŸ’› ## License diff --git a/site/.nojekyll b/site/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/site/assets/img/favicon.ico b/site/assets/img/favicon.ico new file mode 100644 index 0000000..b880b8d Binary files /dev/null and b/site/assets/img/favicon.ico differ diff --git a/site/assets/img/hero.jpg b/site/assets/img/hero.jpg new file mode 100644 index 0000000..7d30e94 Binary files /dev/null and b/site/assets/img/hero.jpg differ diff --git a/site/assets/img/logo-180.png b/site/assets/img/logo-180.png new file mode 100644 index 0000000..2c7def9 Binary files /dev/null and b/site/assets/img/logo-180.png differ diff --git a/site/assets/img/logo-256.png b/site/assets/img/logo-256.png new file mode 100644 index 0000000..368bc1f Binary files /dev/null and b/site/assets/img/logo-256.png differ diff --git a/site/assets/img/logo-32.png b/site/assets/img/logo-32.png new file mode 100644 index 0000000..9244980 Binary files /dev/null and b/site/assets/img/logo-32.png differ diff --git a/site/assets/img/logo-512.png b/site/assets/img/logo-512.png new file mode 100644 index 0000000..0415f29 Binary files /dev/null and b/site/assets/img/logo-512.png differ diff --git a/site/assets/img/logo-64.png b/site/assets/img/logo-64.png new file mode 100644 index 0000000..3af2aa6 Binary files /dev/null and b/site/assets/img/logo-64.png differ diff --git a/site/assets/img/og-image.jpg b/site/assets/img/og-image.jpg new file mode 100644 index 0000000..21db4be Binary files /dev/null and b/site/assets/img/og-image.jpg differ diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..b428dda --- /dev/null +++ b/site/index.html @@ -0,0 +1,222 @@ + + + + + + SurvivorCore β€” the survival game engine for Roblox + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ + + v0.5.0 Β· pre-release + +

The survival game engine for Roblox

+

+ A batteries-included, creator-extensible framework. Survival stats, inventory, gathering, + crafting, combat, mobs & AI β€” wired together, restyleable, and authorable + without code. If you know Roblox Studio, you can build a survival game. +

+ +

Free & open source (MIT) Β· drop-in .rbxm or Wally

+
+
+ + +
+
+

Batteries included

+

The core survival loop ships working β€” stats, inventory, gather, craft, fight β€” not a pile of parts to wire.

+
+
+

No-code friendly

+

A Studio admin plugin creates items, weapons, ammo and mobs β€” with full stats β€” and zero scripting.

+
+
+

Yours to re-skin

+

UI is real, restyleable Instances driven by attributes. Override any default; nothing is locked.

+
+
+

Server-authoritative

+

Hits, harvests and crafts are validated on the server. Content-free by design β€” you bring the game.

+
+
+ + +
+
+

Everything the survival loop needs

+

Each system is configurable, hook-extensible, and authorable as tagged Instances or no-code.

+
+
+
+ +

Survival stats & HUD

+

Health, energy, hunger, thirst, fatigue, blood, poison β€” ticking, draining, biting back. A clean, restyleable HUD drives only the data.

+
+
+ +

Inventory, hotbar & crafting

+

Slots + carry-weight, a 9-slot hotbar, equipment, and hand crafting in a tabbed menu β€” server-authoritative, drag-and-drop.

+
+
+ +

Gathering

+

Tag any mesh Gatherable: hold-E or tool-swing, per-hit yields, reaction hooks (a reed sways, a tree fells and leaves a stump).

+
+
+ +

Combat β€” melee & bow

+

Click to swing; or aim a bow, draw for power, and loose arrows that arc under real weight. One server-validated kill-event schema.

+
+
+ +

Mobs & AI

+

A shared FSM substrate: hostile mobs chase & attack, passive animals flee. Behavior is data β€” faction picks the profile.

+
+
+ +

No-code admin plugin

+

Create items, weapons, ammo and mobs from a Studio form β€” damage, range, arrow curve, weight, aggro, leash β€” and drop them into the world.

+
+
+
+ + +
+
+

See it in action

+

The whole UI suite runs out of the box β€” restyle it, or keep the clean defaults.

+
+
+ SurvivorCore in Roblox Studio: the survival HUD, a tabbed inventory/crafting menu, and the hotbar +
HUD, tabbed menu (inventory Β· character Β· crafting Β· codex Β· achievements Β· quests) and hotbar β€” all default UI.
+
+
+
Mobs & combat
+
Gathering & crafting
+
No-code creation
+
Inventory & hotbar
+
+
+ + +
+
+

Get started

+

Drop it into ReplicatedStorage, register your content, and call start().

+
+
+
+

Install

+

Grab the drop-in model from the latest release, or add it with Wally:

+
# wally.toml
+[dependencies]
+SurvivorCore = "temujincalidius/survivorcore@0.5.0"
+

…or drop SurvivorCore.rbxm into ReplicatedStorage.

+

+ Latest release β†— + Read the docs β†— +

+
+
+

Boot it

+
local SurvivorCore = require(ReplicatedStorage.SurvivorCore)
+
+-- register content (or author it no-code in the admin plugin)
+SurvivorCore.Items.register({ id = "berry", name = "Wild Berries", stack = 20 })
+SurvivorCore.Mobs.register({ id = "husk", faction = "hostile", health = 60 })
+
+SurvivorCore.start()  -- boots stats, inventory, gathering, crafting, combat, mobs…
+

The HUD loader calls startClient() for you. That's it.

+
+
+
+
+ + + + diff --git a/site/styles.css b/site/styles.css new file mode 100644 index 0000000..faa1324 --- /dev/null +++ b/site/styles.css @@ -0,0 +1,314 @@ +/* SurvivorCore landing page β€” matches docs/design-language.md + (dark translucent panels, charcoal / ember-orange / survival-green). */ +:root { + --bg: #14171e; + --bg-2: #0f1217; + --panel: rgba(20, 23, 30, 0.55); + --panel-solid: #1a1e27; + --stroke: rgba(210, 220, 245, 0.12); + --stroke-strong: rgba(210, 220, 245, 0.22); + --text: #f1f3f8; + --muted: #a6adbd; + --ember: #f0883c; + --green: #5acd78; + --radius: 14px; + --maxw: 1120px; + --font: "Manrope", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; +} + +* { box-sizing: border-box; } +html { scroll-behavior: smooth; } +body { + margin: 0; + background: var(--bg); + color: var(--text); + font-family: var(--font); + font-size: 17px; + line-height: 1.6; + -webkit-font-smoothing: antialiased; + overflow-x: hidden; +} +a { color: var(--green); text-decoration: none; } +a:hover { color: #7fe39a; } +code, pre { + font-family: ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", monospace; + font-size: 0.86em; +} +:focus-visible { outline: 2px solid var(--green); outline-offset: 3px; border-radius: 6px; } + +h1, h2, h3 { line-height: 1.15; letter-spacing: -0.015em; margin: 0; } +.accent { color: var(--green); } + +/* ---- Buttons ---- */ +.btn { + display: inline-flex; + align-items: center; + gap: 0.4em; + padding: 0.7em 1.25em; + border-radius: 11px; + font-weight: 700; + font-size: 0.96rem; + border: 1px solid transparent; + transition: transform 0.12s ease, background 0.2s ease, border-color 0.2s ease; + cursor: pointer; +} +.btn:hover { transform: translateY(-1px); } +.btn-primary { background: var(--green); color: #08120b; } +.btn-primary:hover { background: #6fe08c; color: #08120b; } +.btn-ghost { + background: rgba(255, 255, 255, 0.03); + border-color: var(--stroke-strong); + color: var(--text); +} +.btn-ghost:hover { border-color: var(--green); color: var(--text); background: rgba(90, 205, 120, 0.08); } + +/* ---- Nav ---- */ +.nav { + position: sticky; + top: 0; + z-index: 50; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + padding: 0.75rem clamp(1rem, 4vw, 2rem); + background: rgba(15, 18, 23, 0.72); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--stroke); +} +.brand { + display: flex; + align-items: center; + gap: 0.55rem; + font-weight: 800; + font-size: 1.15rem; + color: var(--text); + letter-spacing: -0.02em; +} +.brand img { display: block; } +.nav-links { display: flex; align-items: center; gap: 1.4rem; } +.nav-links > a { color: var(--muted); font-weight: 600; font-size: 0.95rem; } +.nav-links > a:hover { color: var(--text); } +.nav-links .btn { padding: 0.5em 1em; } + +/* ---- Hero ---- */ +.hero { position: relative; isolation: isolate; overflow: hidden; } +.hero-bg { + position: absolute; + inset: 0; + z-index: -1; + background: url("assets/img/hero.jpg") center 38% / cover no-repeat; +} +.hero-bg::after { + content: ""; + position: absolute; + inset: 0; + background: + radial-gradient(120% 80% at 50% 20%, transparent 30%, rgba(15, 18, 23, 0.5) 100%), + linear-gradient(180deg, rgba(15, 18, 23, 0.62) 0%, rgba(15, 18, 23, 0.35) 38%, rgba(20, 23, 30, 0.92) 92%, var(--bg) 100%); +} +.hero-inner { + max-width: 800px; + margin: 0 auto; + padding: clamp(3.5rem, 9vw, 7rem) clamp(1.2rem, 4vw, 2rem) clamp(3rem, 7vw, 5.5rem); + text-align: center; +} +.hero-logo { + width: clamp(84px, 12vw, 112px); + height: auto; + filter: drop-shadow(0 8px 26px rgba(0, 0, 0, 0.45)); + margin-bottom: 1.2rem; +} +.badge { + display: inline-block; + margin-bottom: 1.1rem; + padding: 0.32em 0.85em; + font-size: 0.8rem; + font-weight: 700; + letter-spacing: 0.02em; + color: var(--green); + background: rgba(90, 205, 120, 0.1); + border: 1px solid rgba(90, 205, 120, 0.3); + border-radius: 999px; +} +.badge:hover { color: #7fe39a; } +h1 { + font-size: clamp(2.3rem, 6vw, 3.7rem); + font-weight: 800; +} +.lede { + margin: 1.1rem auto 0; + max-width: 620px; + color: #d6dbe6; + font-size: clamp(1.02rem, 2.4vw, 1.18rem); +} +.lede strong { color: var(--text); } +.cta { margin-top: 1.8rem; display: flex; gap: 0.8rem; justify-content: center; flex-wrap: wrap; } +.hero-sub { margin-top: 1.3rem; color: var(--muted); font-size: 0.9rem; } +.hero-sub code { color: #cdd3df; } + +/* ---- Value props ---- */ +.props { + max-width: var(--maxw); + margin: clamp(2.5rem, 6vw, 4.5rem) auto 0; + padding: 0 clamp(1.2rem, 4vw, 2rem); + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1rem; +} +.prop { + padding: 1.3rem 1.3rem 1.4rem; + background: var(--panel); + border: 1px solid var(--stroke); + border-radius: var(--radius); + backdrop-filter: blur(8px); +} +.prop h3 { font-size: 1.06rem; font-weight: 700; } +.prop p { margin: 0.5rem 0 0; color: var(--muted); font-size: 0.95rem; } + +/* ---- Sections ---- */ +.section { + max-width: var(--maxw); + margin: 0 auto; + padding: clamp(3.5rem, 8vw, 6rem) clamp(1.2rem, 4vw, 2rem) 0; +} +.section-head { text-align: center; max-width: 640px; margin: 0 auto clamp(2rem, 5vw, 3rem); } +.section-head h2 { font-size: clamp(1.7rem, 4vw, 2.4rem); font-weight: 800; } +.section-head p { color: var(--muted); margin: 0.7rem 0 0; font-size: 1.05rem; } + +/* ---- Feature grid ---- */ +.grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.1rem; +} +.card { + padding: 1.5rem; + background: var(--panel); + border: 1px solid var(--stroke); + border-radius: var(--radius); + backdrop-filter: blur(8px); + transition: transform 0.15s ease, border-color 0.2s ease, background 0.2s ease; +} +.card:hover { transform: translateY(-3px); border-color: var(--stroke-strong); background: rgba(26, 30, 39, 0.6); } +.card h3 { font-size: 1.18rem; font-weight: 700; margin-top: 1rem; } +.card p { color: var(--muted); margin: 0.5rem 0 0; font-size: 0.97rem; } +.card code { color: #cdd3df; background: rgba(255, 255, 255, 0.05); padding: 0.05em 0.35em; border-radius: 5px; } +.ico { + display: grid; + place-items: center; + width: 46px; + height: 46px; + border-radius: 12px; +} +.ico svg { width: 24px; height: 24px; } +.ico-green { color: var(--green); background: rgba(90, 205, 120, 0.12); } +.ico-ember { color: var(--ember); background: rgba(240, 136, 60, 0.13); } + +/* ---- Showcase ---- */ +.shot { + margin: 0; + border: 1px solid var(--stroke-strong); + border-radius: var(--radius); + overflow: hidden; + background: var(--panel-solid); + box-shadow: 0 30px 70px -30px rgba(0, 0, 0, 0.7); +} +.shot img { display: block; width: 100%; height: auto; } +.shot figcaption { + padding: 0.8rem 1.1rem; + color: var(--muted); + font-size: 0.9rem; + border-top: 1px solid var(--stroke); + text-align: center; +} +.videos { + margin-top: 1.4rem; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.1rem; +} +.video { + background: var(--panel-solid); + border: 1px solid var(--stroke); + border-radius: var(--radius); + overflow: hidden; +} +.video iframe { display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; } +.video span { + display: block; + padding: 0.65rem 1rem; + font-weight: 600; + font-size: 0.92rem; + color: var(--muted); + border-top: 1px solid var(--stroke); +} + +/* ---- Get started ---- */ +.start-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem; } +.panel { + padding: 1.6rem; + background: var(--panel); + border: 1px solid var(--stroke); + border-radius: var(--radius); + backdrop-filter: blur(8px); +} +.panel h3 { font-size: 1.2rem; font-weight: 700; } +.panel p { color: var(--muted); } +.panel p.muted { font-size: 0.9rem; } +.panel .btn { margin-top: 0.4rem; margin-right: 0.5rem; } +pre { + margin: 1rem 0; + padding: 1rem 1.1rem; + background: #0c0f14; + border: 1px solid var(--stroke); + border-radius: 10px; + overflow-x: auto; + color: #d7dce6; + line-height: 1.55; +} +pre code { color: inherit; } + +/* ---- Footer ---- */ +.footer { + margin-top: clamp(3.5rem, 8vw, 6rem); + padding: clamp(2rem, 5vw, 3rem) clamp(1.2rem, 4vw, 2rem) 2.5rem; + border-top: 1px solid var(--stroke); + background: var(--bg-2); +} +.footer-inner { + max-width: var(--maxw); + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.2rem; + flex-wrap: wrap; +} +.footer-brand { display: flex; align-items: center; gap: 0.5rem; font-weight: 800; } +.footer-links { display: flex; gap: 1.3rem; flex-wrap: wrap; } +.footer-links a { color: var(--muted); font-weight: 600; font-size: 0.95rem; } +.footer-links a:hover { color: var(--text); } +.colophon { + max-width: var(--maxw); + margin: 1.5rem auto 0; + color: #6f7686; + font-size: 0.86rem; +} + +/* ---- Responsive ---- */ +@media (max-width: 900px) { + .props { grid-template-columns: repeat(2, 1fr); } + .grid { grid-template-columns: repeat(2, 1fr); } + .start-grid { grid-template-columns: 1fr; } +} +@media (max-width: 680px) { + .nav-links { gap: 0.8rem; } + .nav-links > a:not(.btn) { display: none; } + .props { grid-template-columns: 1fr; } + .grid { grid-template-columns: 1fr; } + .videos { grid-template-columns: 1fr; } +} +@media (prefers-reduced-motion: reduce) { + * { scroll-behavior: auto; transition: none !important; } +}