Day 1 vibes
This commit is contained in:
+270
@@ -0,0 +1,270 @@
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
|
||||
# User-specific files
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Ll]og/
|
||||
|
||||
# Visual Studio 2015 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# DNX
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
publish_release/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# TODO: Comment the next line if you want to checkin your web deploy settings
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
#*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/packages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/packages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/packages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignoreable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
node_modules/
|
||||
orleans.codegen.cs
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
**/.DS_Store
|
||||
|
||||
**/.vs/
|
||||
.DS_Store
|
||||
|
||||
|
||||
publish_release/
|
||||
@@ -0,0 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,247 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.Json;
|
||||
|
||||
var exeDir = AppContext.BaseDirectory;
|
||||
|
||||
var solutionDir = FindContainingDir(exeDir, "ET.sln")
|
||||
?? throw new InvalidOperationException("Cannot find ET.sln");
|
||||
|
||||
var srcDir = Path.GetFullPath(Path.Combine(solutionDir, "..", "docs", "Notes"));
|
||||
var dstDir = Path.GetFullPath(Path.Combine(solutionDir, "Web", "wwwroot", "docs", "notes"));
|
||||
|
||||
Console.WriteLine($"Source: {srcDir}");
|
||||
Console.WriteLine($"Destination: {dstDir}");
|
||||
|
||||
if (!Directory.Exists(srcDir))
|
||||
{
|
||||
Console.Error.WriteLine($"Source directory not found: {srcDir}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
SyncNotes(srcDir, dstDir);
|
||||
GenerateMap(srcDir, Path.GetFullPath(Path.Combine(solutionDir, "Web", "wwwroot", "docs")));
|
||||
|
||||
Console.WriteLine("Done.");
|
||||
return 0;
|
||||
|
||||
static void SyncNotes(string srcDir, string dstDir)
|
||||
{
|
||||
Directory.CreateDirectory(dstDir);
|
||||
|
||||
foreach (var file in Directory.GetFiles(dstDir))
|
||||
File.Delete(file);
|
||||
|
||||
var entries = new List<object>();
|
||||
|
||||
foreach (var file in Directory.EnumerateFiles(srcDir, "*.md"))
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(file);
|
||||
var slug = Slugify(name);
|
||||
|
||||
File.Copy(file, Path.Combine(dstDir, $"{slug}.md"), overwrite: true);
|
||||
|
||||
string? category = null;
|
||||
var content = File.ReadAllText(file);
|
||||
var fmMatch = Regex.Match(content, @"^---\s*\n(.*?)\n---", RegexOptions.Singleline);
|
||||
if (fmMatch.Success)
|
||||
{
|
||||
var catMatch = Regex.Match(fmMatch.Groups[1].Value, @"(?m)^category:\s*(.+)$");
|
||||
if (catMatch.Success)
|
||||
category = catMatch.Groups[1].Value.Trim().Trim('"');
|
||||
}
|
||||
|
||||
var entry = new Dictionary<string, object?>
|
||||
{
|
||||
["slug"] = slug,
|
||||
["title"] = name
|
||||
};
|
||||
if (category != null)
|
||||
entry["category"] = category;
|
||||
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
var index = new Dictionary<string, object> { ["notes"] = entries };
|
||||
var json = JsonSerializer.Serialize(index, new JsonSerializerOptions { WriteIndented = true });
|
||||
|
||||
var indexPath = Path.Combine(Path.GetDirectoryName(dstDir)!, "notes-index.json");
|
||||
File.WriteAllText(indexPath, json);
|
||||
|
||||
Console.WriteLine($"Copied {entries.Count} notes.");
|
||||
Console.WriteLine($"Index written to: {indexPath}");
|
||||
}
|
||||
|
||||
static void GenerateMap(string srcDir, string dstDir)
|
||||
{
|
||||
var terrainColors = new Dictionary<string, string>
|
||||
{
|
||||
["Grass"] = "#4caf50",
|
||||
["Forest"] = "#2e7d32",
|
||||
["Mountain"] = "#78909c",
|
||||
["Water"] = "#42a5f5",
|
||||
["Wasteland"] = "#8d6e63",
|
||||
};
|
||||
|
||||
var regionFiles = Directory.EnumerateFiles(srcDir, "*.md")
|
||||
.Select(f => (File: f, Content: File.ReadAllText(f)))
|
||||
.Where(t => Regex.IsMatch(t.Content, @"(?m)^category:\s*Region\s*$", RegexOptions.Multiline))
|
||||
.ToList();
|
||||
|
||||
var regions = new List<RegionData>();
|
||||
|
||||
foreach (var (file, content) in regionFiles)
|
||||
{
|
||||
var fmMatch = Regex.Match(content, @"^---\s*\n(.*?)\n---", RegexOptions.Singleline);
|
||||
if (!fmMatch.Success) continue;
|
||||
|
||||
var fm = fmMatch.Groups[1].Value;
|
||||
var name = Path.GetFileNameWithoutExtension(file);
|
||||
|
||||
var xMatch = Regex.Match(fm, @"(?m)^X:\s*(\d+)");
|
||||
var yMatch = Regex.Match(fm, @"(?m)^Y:\s*(\d+)");
|
||||
if (!xMatch.Success || !yMatch.Success) continue;
|
||||
|
||||
var connMatches = Regex.Matches(fm, @"\[\[([^\]]+)\]\]");
|
||||
var conns = connMatches.Select(m => m.Groups[1].Value).ToList();
|
||||
|
||||
var landmarks = new List<string>();
|
||||
var lmSection = Regex.Match(fm, @"Landmarks:\s*\n((?:\s*-\s*""\[\[([^\]]+)\]\]""\s*\n?)*)");
|
||||
if (lmSection.Success)
|
||||
{
|
||||
var lmEntries = Regex.Matches(lmSection.Groups[1].Value, @"\[\[([^\]]+)\]\]");
|
||||
landmarks.AddRange(lmEntries.Select(m => m.Groups[1].Value));
|
||||
}
|
||||
|
||||
var terrain = name.Split(' ')[0];
|
||||
|
||||
regions.Add(new RegionData
|
||||
{
|
||||
Name = name,
|
||||
Slug = Slugify(name),
|
||||
Terrain = terrain,
|
||||
X = int.Parse(xMatch.Groups[1].Value),
|
||||
Y = int.Parse(yMatch.Groups[1].Value),
|
||||
Connections = conns,
|
||||
Landmarks = landmarks
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var srcMapImage = Path.GetFullPath(Path.Combine(srcDir, "..", "Images", "Map.png"));
|
||||
var dstMapImage = Path.Combine(dstDir, "Map.png");
|
||||
if (File.Exists(srcMapImage))
|
||||
File.Copy(srcMapImage, dstMapImage, overwrite: true);
|
||||
|
||||
var nameLookup = regions.ToDictionary(r => r.Name);
|
||||
|
||||
var pad = 60;
|
||||
var maxX = (regions.Count > 0 ? regions.Max(r => r.X) : 0) + pad * 2;
|
||||
var maxY = (regions.Count > 0 ? regions.Max(r => r.Y) : 0) + pad * 2;
|
||||
var svg = new System.Text.StringBuilder();
|
||||
svg.AppendLine("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 700 461\">");
|
||||
|
||||
svg.AppendLine("""<image href="Map.png" width="700" height="461" preserveAspectRatio="xMidYMid meet"/>""");
|
||||
|
||||
svg.AppendLine($"""<defs>""");
|
||||
foreach (var (terrain, color) in terrainColors)
|
||||
{
|
||||
svg.AppendLine($"""<radialGradient id="glow-{terrain}" cx="50%" cy="50%" r="50%">""");
|
||||
svg.AppendLine($"""<stop offset="0%" stop-color="{color}" stop-opacity="0.3"/>""");
|
||||
svg.AppendLine($"""<stop offset="100%" stop-color="{color}" stop-opacity="0"/>""");
|
||||
svg.AppendLine("</radialGradient>");
|
||||
}
|
||||
svg.AppendLine("</defs>");
|
||||
|
||||
foreach (var region in regions)
|
||||
{
|
||||
foreach (var conn in region.Connections)
|
||||
{
|
||||
if (!nameLookup.TryGetValue(conn, out var target) || string.Compare(region.Name, conn, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
continue;
|
||||
|
||||
svg.AppendLine($"""<line x1="{region.X}" y1="{region.Y}" x2="{target.X}" y2="{target.Y}" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>""");
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var region in regions)
|
||||
{
|
||||
var cx = region.X;
|
||||
var cy = region.Y;
|
||||
var color = terrainColors.GetValueOrDefault(region.Terrain, "#888");
|
||||
|
||||
svg.AppendLine($"""<a href="/docs/{region.Slug}" target="_top">""");
|
||||
svg.AppendLine($"""<circle cx="{cx}" cy="{cy}" r="32" fill="url(#glow-{region.Terrain})"/>""");
|
||||
svg.AppendLine($"""<circle cx="{cx}" cy="{cy}" r="18" fill="{color}cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>""");
|
||||
|
||||
var labelX = cx + 24;
|
||||
svg.AppendLine($"""<text x="{labelX}" y="{cy + 4}" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">""");
|
||||
svg.Append(System.Text.Encodings.Web.HtmlEncoder.Default.Encode(region.Name));
|
||||
svg.AppendLine("</text>");
|
||||
|
||||
foreach (var lm in region.Landmarks)
|
||||
{
|
||||
svg.AppendLine($"""<text x="{labelX}" y="{cy + 18}" fill="rgba(255,255,255,0.45)" font-family="system-ui,sans-serif" font-size="9" font-style="italic">""");
|
||||
svg.Append(System.Text.Encodings.Web.HtmlEncoder.Default.Encode($"\u2605 {lm}"));
|
||||
svg.AppendLine("</text>");
|
||||
}
|
||||
|
||||
svg.AppendLine("</a>");
|
||||
}
|
||||
|
||||
/**
|
||||
var legendX = 700 - 160;
|
||||
var legendY = 20;
|
||||
svg.AppendLine($"""<rect x="{legendX}" y="{legendY}" width="140" height="{20 + terrainColors.Count * 22}" rx="6" fill="rgba(0,0,0,0.4)" stroke="rgba(255,255,255,0.08)"/>""");
|
||||
svg.AppendLine($"""<text x="{legendX + 10}" y="{legendY + 15}" fill="rgba(255,255,255,0.6)" font-family="system-ui,sans-serif" font-size="10" font-weight="600">Legend</text>""");
|
||||
var ly = legendY + 32;
|
||||
foreach (var (terrain, color) in terrainColors)
|
||||
{
|
||||
svg.AppendLine($"""<circle cx="{legendX + 14}" cy="{ly - 3}" r="5" fill="{color}"/>""");
|
||||
svg.AppendLine($"""<text x="{legendX + 26}" y="{ly + 1}" fill="rgba(255,255,255,0.7)" font-family="system-ui,sans-serif" font-size="10">{terrain}</text>""");
|
||||
ly += 22;
|
||||
}
|
||||
*/
|
||||
|
||||
svg.AppendLine("</svg>");
|
||||
|
||||
var mapPath = Path.Combine(dstDir, "map.svg");
|
||||
File.WriteAllText(mapPath, svg.ToString());
|
||||
Console.WriteLine($"Map written to: {mapPath} ({regions.Count} regions)");
|
||||
}
|
||||
|
||||
static string Slugify(string name)
|
||||
{
|
||||
var slug = name.ToLowerInvariant()
|
||||
.Replace("'", "")
|
||||
.Replace(".", "")
|
||||
.Replace("(", "")
|
||||
.Replace(")", "");
|
||||
slug = Regex.Replace(slug, @"[^a-z0-9 -]", "");
|
||||
slug = Regex.Replace(slug, @"\s+", "-");
|
||||
slug = Regex.Replace(slug, @"-+", "-");
|
||||
return slug.Trim('-');
|
||||
}
|
||||
|
||||
static string? FindContainingDir(string startDir, string markerFile)
|
||||
{
|
||||
var dir = new DirectoryInfo(startDir);
|
||||
while (dir != null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(dir.FullName, markerFile)))
|
||||
return dir.FullName;
|
||||
dir = dir.Parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class RegionData
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Slug { get; set; } = "";
|
||||
public string Terrain { get; set; } = "";
|
||||
public int X { get; set; }
|
||||
public int Y { get; set; }
|
||||
public List<string> Connections { get; set; } = new();
|
||||
public List<string> Landmarks { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "Web\Web.csproj", "{6E375519-4DF8-455B-9AEC-0C84C31106E9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console", "Console\Console.csproj", "{A5946F7D-E8CE-4ACE-8D79-0F5FF0CB11C0}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6E375519-4DF8-455B-9AEC-0C84C31106E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6E375519-4DF8-455B-9AEC-0C84C31106E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6E375519-4DF8-455B-9AEC-0C84C31106E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6E375519-4DF8-455B-9AEC-0C84C31106E9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A5946F7D-E8CE-4ACE-8D79-0F5FF0CB11C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A5946F7D-E8CE-4ACE-8D79-0F5FF0CB11C0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A5946F7D-E8CE-4ACE-8D79-0F5FF0CB11C0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A5946F7D-E8CE-4ACE-8D79-0F5FF0CB11C0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,6 @@
|
||||
<Router AppAssembly="@typeof(App).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
|
||||
</Found>
|
||||
</Router>
|
||||
@@ -0,0 +1,11 @@
|
||||
@inherits LayoutComponentBase
|
||||
<div class="page">
|
||||
<div class="sidebar">
|
||||
<NavMenu/>
|
||||
</div>
|
||||
<main>
|
||||
<article class="content px-4">
|
||||
@Body
|
||||
</article>
|
||||
</main>
|
||||
</div>
|
||||
@@ -0,0 +1,77 @@
|
||||
.page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
|
||||
}
|
||||
|
||||
.top-row {
|
||||
background-color: #f7f7f7;
|
||||
border-bottom: 1px solid #d6d5d5;
|
||||
justify-content: flex-end;
|
||||
height: 3.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
white-space: nowrap;
|
||||
margin-left: 1.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.top-row ::deep a:first-child {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (max-width: 640.98px) {
|
||||
.top-row {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.top-row ::deep a, .top-row ::deep .btn-link {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.page {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.top-row.auth ::deep a:first-child {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.top-row, article {
|
||||
padding-left: 2rem !important;
|
||||
padding-right: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
@inject Web.Services.DocsService DocsService
|
||||
|
||||
<div class="top-row ps-3 navbar navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="">Web</a>
|
||||
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="@NavMenuCssClass nav-scrollable" @onclick="ToggleNavMenu">
|
||||
<nav class="nav flex-column">
|
||||
<div class="nav-item px-3">
|
||||
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
|
||||
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
|
||||
</NavLink>
|
||||
</div>
|
||||
|
||||
@if (groupedNotes == null)
|
||||
{
|
||||
<div class="nav-item px-3"><span class="nav-link text-secondary">Loading...</span></div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var group in groupedNotes)
|
||||
{
|
||||
<div class="nav-item px-3 nav-section-header">@group.Category</div>
|
||||
@foreach (var note in group.Notes)
|
||||
{
|
||||
<div class="nav-item px-3 nav-item-doc">
|
||||
<NavLink class="nav-link" href="@($"docs/{note.Slug}")">
|
||||
@note.Title
|
||||
</NavLink>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool collapseNavMenu = true;
|
||||
private List<NoteGroup>? groupedNotes;
|
||||
|
||||
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
|
||||
|
||||
private void ToggleNavMenu()
|
||||
{
|
||||
collapseNavMenu = !collapseNavMenu;
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var index = await DocsService.GetIndexAsync();
|
||||
groupedNotes = (index.Notes ?? new())
|
||||
.GroupBy(n => string.IsNullOrEmpty(n.Category) ? "Uncategorized" : n.Category)
|
||||
.OrderBy(g => g.Key)
|
||||
.Select(g => new NoteGroup { Category = g.Key, Notes = g.OrderBy(n => n.Title).ToList() })
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private class NoteGroup
|
||||
{
|
||||
public string Category { get; set; } = "";
|
||||
public List<Web.Models.NoteInfo> Notes { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
.navbar-toggler {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.top-row {
|
||||
min-height: 3.5rem;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
margin-right: 0.75rem;
|
||||
top: -1px;
|
||||
background-size: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bi-house-door-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-plus-square-fill-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-list-nested-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.bi-file-text-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-file-text' viewBox='0 0 16 16'%3E%3Cpath d='M5 4a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1H5zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zM5 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1H5zm0 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1H5z'/%3E%3Cpath d='M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
font-size: 0.9rem;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.nav-item ::deep a {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
border-radius: 0;
|
||||
height: 2.6rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: 2.6rem;
|
||||
padding: 0 1rem;
|
||||
border-left: 3px solid transparent;
|
||||
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
||||
}
|
||||
|
||||
.nav-item ::deep a.active {
|
||||
background-color: rgba(255, 255, 255, 0.12);
|
||||
color: white;
|
||||
border-left-color: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.nav-item ::deep a:hover {
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item-doc ::deep a {
|
||||
padding-left: 1.75rem !important;
|
||||
font-size: 0.8rem;
|
||||
height: 1.85rem !important;
|
||||
line-height: 1.85rem !important;
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
.nav-item-doc ::deep a.active {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
border-left-color: #6ea8fe;
|
||||
}
|
||||
|
||||
.nav-item-doc ::deep a:hover {
|
||||
background-color: rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.nav-section-header {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
padding: 1.25rem 1rem 0.35rem;
|
||||
margin-top: 0.25rem;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.nav-section-header:first-of-type {
|
||||
border-top: none;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255,255,255,0.15) transparent;
|
||||
}
|
||||
|
||||
.nav-scrollable::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.nav-scrollable::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.nav-scrollable::-webkit-scrollbar-thumb {
|
||||
background: rgba(255,255,255,0.15);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@media (min-width: 641px) {
|
||||
.navbar-toggler {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapse {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-scrollable {
|
||||
height: calc(100vh - 3.5rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Web.Models;
|
||||
|
||||
public class NoteInfo
|
||||
{
|
||||
public string Slug { get; set; } = "";
|
||||
public string Title { get; set; } = "";
|
||||
public string? Category { get; set; }
|
||||
}
|
||||
|
||||
public class NotesIndex
|
||||
{
|
||||
public List<NoteInfo> Notes { get; set; } = new();
|
||||
}
|
||||
|
||||
public class NoteDocument
|
||||
{
|
||||
public string Slug { get; set; } = "";
|
||||
public string Title { get; set; } = "";
|
||||
public string? Category { get; set; }
|
||||
public string FrontmatterHtml { get; set; } = "";
|
||||
public string HtmlContent { get; set; } = "";
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
@page "/docs/{Slug}"
|
||||
@inject Web.Services.DocsService DocsService
|
||||
|
||||
<PageTitle>@(doc?.Title ?? "Not Found")</PageTitle>
|
||||
|
||||
@if (loading)
|
||||
{
|
||||
<p>Loading...</p>
|
||||
}
|
||||
else if (doc == null)
|
||||
{
|
||||
<h1>Not Found</h1>
|
||||
<p>The document "@Slug" could not be found.</p>
|
||||
<a href="/docs">← Back to Docs</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h1>
|
||||
@doc.Title
|
||||
@if (!string.IsNullOrEmpty(doc.Category))
|
||||
{
|
||||
<span class="category-badge">@doc.Category</span>
|
||||
}
|
||||
</h1>
|
||||
|
||||
@if (!string.IsNullOrEmpty(doc.FrontmatterHtml))
|
||||
{
|
||||
<details class="frontmatter-section" open>
|
||||
<summary>Frontmatter</summary>
|
||||
@((MarkupString)doc.FrontmatterHtml)
|
||||
</details>
|
||||
}
|
||||
|
||||
<div class="markdown-body">
|
||||
@((MarkupString)doc.HtmlContent)
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public string Slug { get; set; } = "";
|
||||
|
||||
private Web.Models.NoteDocument? doc;
|
||||
private bool loading = true;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
loading = true;
|
||||
doc = null;
|
||||
doc = await DocsService.GetNoteAsync(Slug);
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@page "/docs"
|
||||
|
||||
<PageTitle>Docs</PageTitle>
|
||||
|
||||
<h1>Documentation</h1>
|
||||
|
||||
<p>Select a note from the sidebar to view its contents.</p>
|
||||
@@ -0,0 +1,9 @@
|
||||
@page "/"
|
||||
|
||||
<PageTitle>Earthborne Trailblazer</PageTitle>
|
||||
|
||||
<h1>Earthborne Trailblazer</h1>
|
||||
|
||||
<div class="map-container">
|
||||
<object data="docs/map.svg" type="image/svg+xml" class="map-svg"></object>
|
||||
</div>
|
||||
@@ -0,0 +1,5 @@
|
||||
@page "/not-found"
|
||||
@layout MainLayout
|
||||
|
||||
<h3>Not Found</h3>
|
||||
<p>Sorry, the content you are looking for does not exist.</p>
|
||||
@@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Web;
|
||||
using Web.Services;
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
|
||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||
builder.Services.AddScoped<DocsService>();
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "http://localhost:5161",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:7083;http://localhost:5161",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
using System.Net.Http.Json;
|
||||
using Markdig;
|
||||
using Web.Models;
|
||||
|
||||
namespace Web.Services;
|
||||
|
||||
public class DocsService
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
private NotesIndex? _index;
|
||||
private static readonly MarkdownPipeline Pipeline = new MarkdownPipelineBuilder()
|
||||
.UseYamlFrontMatter()
|
||||
.Build();
|
||||
|
||||
public DocsService(HttpClient http)
|
||||
{
|
||||
_http = http;
|
||||
}
|
||||
|
||||
public async Task<NotesIndex> GetIndexAsync()
|
||||
{
|
||||
if (_index != null)
|
||||
return _index;
|
||||
|
||||
_index = await _http.GetFromJsonAsync<NotesIndex>("docs/notes-index.json")
|
||||
?? new NotesIndex();
|
||||
|
||||
return _index;
|
||||
}
|
||||
|
||||
public async Task<NoteDocument?> GetNoteAsync(string slug)
|
||||
{
|
||||
var index = await GetIndexAsync();
|
||||
var noteInfo = index.Notes.FirstOrDefault(n =>
|
||||
string.Equals(n.Slug, slug, StringComparison.OrdinalIgnoreCase));
|
||||
if (noteInfo == null) return null;
|
||||
|
||||
try
|
||||
{
|
||||
var markdown = await _http.GetStringAsync($"docs/notes/{slug}.md");
|
||||
return ParseDocument(slug, noteInfo.Title, markdown);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static NoteDocument ParseDocument(string slug, string title, string markdown)
|
||||
{
|
||||
var doc = new NoteDocument
|
||||
{
|
||||
Slug = slug,
|
||||
Title = title
|
||||
};
|
||||
|
||||
var frontmatterLines = new List<string>();
|
||||
var bodyLines = new List<string>();
|
||||
var inFrontmatter = false;
|
||||
var frontmatterDone = false;
|
||||
|
||||
foreach (var line in markdown.Replace("\r\n", "\n").Split('\n'))
|
||||
{
|
||||
var trimmed = line.Trim();
|
||||
if (!frontmatterDone && trimmed == "---")
|
||||
{
|
||||
if (!inFrontmatter)
|
||||
{
|
||||
inFrontmatter = true;
|
||||
continue;
|
||||
}
|
||||
inFrontmatter = false;
|
||||
frontmatterDone = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (inFrontmatter)
|
||||
frontmatterLines.Add(line);
|
||||
else if (frontmatterDone || !string.IsNullOrWhiteSpace(line))
|
||||
bodyLines.Add(line);
|
||||
}
|
||||
|
||||
if (frontmatterLines.Count > 0)
|
||||
{
|
||||
foreach (var line in frontmatterLines)
|
||||
{
|
||||
var colonIdx = line.IndexOf(':');
|
||||
if (colonIdx > 0)
|
||||
{
|
||||
var key = line[..colonIdx].Trim();
|
||||
if (string.Equals(key, "category", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
doc.Category = line[(colonIdx + 1)..].Trim().Trim('"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fmHtml = new System.Text.StringBuilder();
|
||||
fmHtml.Append("<table class=\"frontmatter\">");
|
||||
foreach (var line in frontmatterLines)
|
||||
{
|
||||
var colonIdx = line.IndexOf(':');
|
||||
if (colonIdx > 0)
|
||||
{
|
||||
var key = line[..colonIdx].Trim();
|
||||
var value = line[(colonIdx + 1)..].Trim().Trim('"');
|
||||
fmHtml.Append("<tr><td class=\"fm-key\">");
|
||||
fmHtml.Append(System.Net.WebUtility.HtmlEncode(key));
|
||||
fmHtml.Append("</td><td class=\"fm-value\">");
|
||||
var encoded = System.Net.WebUtility.HtmlEncode(value);
|
||||
fmHtml.Append(ConvertWikiLinks(encoded));
|
||||
fmHtml.Append("</td></tr>");
|
||||
}
|
||||
else
|
||||
{
|
||||
fmHtml.Append("<tr><td colspan=\"2\">");
|
||||
fmHtml.Append(ConvertWikiLinks(System.Net.WebUtility.HtmlEncode(line.Trim())));
|
||||
fmHtml.Append("</td></tr>");
|
||||
}
|
||||
}
|
||||
fmHtml.Append("</table>");
|
||||
doc.FrontmatterHtml = fmHtml.ToString();
|
||||
}
|
||||
|
||||
var body = string.Join("\n", bodyLines);
|
||||
body = ConvertWikiLinks(body);
|
||||
doc.HtmlContent = Markdown.ToHtml(body, Pipeline);
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
private static string ConvertWikiLinks(string text)
|
||||
{
|
||||
return System.Text.RegularExpressions.Regex.Replace(
|
||||
text,
|
||||
@"\[\[([^\]]+)\]\]",
|
||||
match =>
|
||||
{
|
||||
var content = match.Groups[1].Value;
|
||||
var parts = content.Split('|');
|
||||
var linkText = parts.Length > 1 ? parts[1].Trim() : parts[0].Trim();
|
||||
var target = parts[0].Trim();
|
||||
var slug = Slugify(target);
|
||||
return $"<a href=\"/docs/{slug}\">{System.Net.WebUtility.HtmlEncode(linkText)}</a>";
|
||||
});
|
||||
}
|
||||
|
||||
public static string Slugify(string title)
|
||||
{
|
||||
var slug = title.ToLowerInvariant()
|
||||
.Replace(' ', '-')
|
||||
.Replace("'", "")
|
||||
.Replace(".", "")
|
||||
.Replace("(", "")
|
||||
.Replace(")", "");
|
||||
slug = System.Text.RegularExpressions.Regex.Replace(slug, @"[^a-z0-9\-]", "");
|
||||
slug = System.Text.RegularExpressions.Regex.Replace(slug, @"-+", "-");
|
||||
return slug.Trim('-');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Markdig" Version="0.40.0"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.9"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.9" PrivateAssets="all"/>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="SyncDocsNotes" BeforeTargets="BeforeBuild">
|
||||
<Exec Command="dotnet run --project "$(MSBuildProjectDirectory)\..\Console\Console.csproj""/>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,12 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Http
|
||||
@using Microsoft.JSInterop
|
||||
@using Web
|
||||
@using Web.Layout
|
||||
@using Web.Models
|
||||
@using Web.Services
|
||||
@@ -0,0 +1,231 @@
|
||||
html, body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a, .btn-link {
|
||||
color: #0071c1;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 1.1rem;
|
||||
}
|
||||
|
||||
.valid.modified:not([type=checkbox]) {
|
||||
outline: 1px solid #26b050;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
outline: 1px solid red;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: red;
|
||||
}
|
||||
|
||||
#blazor-error-ui {
|
||||
color-scheme: light only;
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.loading-progress {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 8rem;
|
||||
height: 8rem;
|
||||
inset: 20vh 0 auto 0;
|
||||
margin: 0 auto 0 auto;
|
||||
}
|
||||
|
||||
.loading-progress circle {
|
||||
fill: none;
|
||||
stroke: #e0e0e0;
|
||||
stroke-width: 0.6rem;
|
||||
transform-origin: 50% 50%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.loading-progress circle:last-child {
|
||||
stroke: #1b6ec2;
|
||||
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
|
||||
transition: stroke-dasharray 0.05s ease-in-out;
|
||||
}
|
||||
|
||||
.loading-progress-text {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
|
||||
}
|
||||
|
||||
.loading-progress-text:after {
|
||||
content: var(--blazor-load-percentage-text, "Loading");
|
||||
}
|
||||
|
||||
code {
|
||||
color: #c02d76;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
|
||||
color: var(--bs-secondary-color);
|
||||
text-align: end;
|
||||
}
|
||||
|
||||
.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.bi-file-text-nav-menu {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-file-text' viewBox='0 0 16 16'%3E%3Cpath d='M5 4a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1H5zm-.5 2.5A.5.5 0 0 1 5 6h6a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zM5 8a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1H5zm0 2a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1H5z'/%3E%3Cpath d='M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2zm10-1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
.docs-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.docs-card {
|
||||
display: block;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: box-shadow 0.15s ease-in-out, border-color 0.15s ease-in-out;
|
||||
}
|
||||
|
||||
.docs-card:hover {
|
||||
border-color: #1b6ec2;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.docs-card h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.frontmatter-section {
|
||||
margin: 1rem 0;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 6px;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.frontmatter-section summary {
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
|
||||
table.frontmatter {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
table.frontmatter td {
|
||||
padding: 0.25rem 0.5rem;
|
||||
border: 1px solid #dee2e6;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
table.frontmatter td.fm-key {
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
white-space: nowrap;
|
||||
width: 1%;
|
||||
background: #e9ecef;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
line-height: 1.7;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.markdown-body h1 { font-size: 1.75rem; margin: 1.5rem 0 0.75rem; }
|
||||
.markdown-body h2 { font-size: 1.4rem; margin: 1.25rem 0 0.5rem; }
|
||||
.markdown-body h3 { font-size: 1.15rem; margin: 1rem 0 0.5rem; }
|
||||
.markdown-body p { margin: 0.5rem 0; }
|
||||
.markdown-body ul, .markdown-body ol { margin: 0.5rem 0; padding-left: 1.5rem; }
|
||||
.markdown-body table { border-collapse: collapse; margin: 0.75rem 0; }
|
||||
.markdown-body th, .markdown-body td { border: 1px solid #dee2e6; padding: 0.4rem 0.6rem; }
|
||||
.markdown-body th { background: #f8f9fa; }
|
||||
.markdown-body code { background: #f0f0f0; padding: 0.15rem 0.3rem; border-radius: 3px; font-size: 0.9em; }
|
||||
.markdown-body pre code { background: none; padding: 0; }
|
||||
.markdown-body blockquote { border-left: 3px solid #dee2e6; padding-left: 1rem; color: #6c757d; margin: 0.75rem 0; }
|
||||
|
||||
.map-container {
|
||||
max-width: 100%;
|
||||
margin: 1.5rem 0;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #1a1a2e;
|
||||
}
|
||||
|
||||
.map-svg {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.category-badge {
|
||||
display: inline-block;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
padding: 0.2em 0.6em;
|
||||
border-radius: 4px;
|
||||
vertical-align: middle;
|
||||
margin-left: 0.5rem;
|
||||
background: #e8f4fd;
|
||||
color: #1b6ec2;
|
||||
border: 1px solid #b8daff;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 589 KiB |
@@ -0,0 +1,185 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 700 461">
|
||||
<image href="Map.png" width="700" height="461" preserveAspectRatio="xMidYMid meet"/>
|
||||
<defs>
|
||||
<radialGradient id="glow-Grass" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#4caf50" stop-opacity="0.3"/>
|
||||
<stop offset="100%" stop-color="#4caf50" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="glow-Forest" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#2e7d32" stop-opacity="0.3"/>
|
||||
<stop offset="100%" stop-color="#2e7d32" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="glow-Mountain" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#78909c" stop-opacity="0.3"/>
|
||||
<stop offset="100%" stop-color="#78909c" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="glow-Water" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#42a5f5" stop-opacity="0.3"/>
|
||||
<stop offset="100%" stop-color="#42a5f5" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="glow-Wasteland" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#8d6e63" stop-opacity="0.3"/>
|
||||
<stop offset="100%" stop-color="#8d6e63" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<line x1="60" y1="330" x2="15" y2="260" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="60" y1="330" x2="150" y2="400" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="60" y1="330" x2="30" y2="410" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="60" y1="330" x2="250" y2="370" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="250" y1="370" x2="150" y2="400" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="250" y1="370" x2="140" y2="260" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="150" y1="125" x2="140" y2="260" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="150" y1="125" x2="50" y2="50" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="420" y1="100" x2="510" y2="30" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="420" y1="100" x2="560" y2="120" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="420" y1="100" x2="550" y2="290" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="470" y1="410" x2="600" y2="400" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="15" y1="260" x2="20" y2="120" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="15" y1="260" x2="140" y2="260" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="300" y1="230" x2="140" y2="260" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="300" y1="230" x2="260" y2="110" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="300" y1="230" x2="380" y2="180" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="510" y1="30" x2="370" y2="30" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="510" y1="30" x2="560" y2="120" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="550" y1="290" x2="430" y2="330" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="550" y1="290" x2="600" y2="400" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="550" y1="290" x2="560" y2="120" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="20" y1="120" x2="50" y2="50" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="260" y1="110" x2="50" y2="50" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="380" y1="180" x2="430" y2="330" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="560" y1="120" x2="640" y2="110" stroke="rgba(255,255,255,0.12)" stroke-width="2" stroke-linecap="round"/>
|
||||
<a href="/docs/forest-1" target="_top">
|
||||
<circle cx="60" cy="330" r="32" fill="url(#glow-Forest)"/>
|
||||
<circle cx="60" cy="330" r="18" fill="#2e7d32cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="84" y="334" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Forest 1</text>
|
||||
</a>
|
||||
<a href="/docs/forest-2" target="_top">
|
||||
<circle cx="250" cy="370" r="32" fill="url(#glow-Forest)"/>
|
||||
<circle cx="250" cy="370" r="18" fill="#2e7d32cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="274" y="374" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Forest 2</text>
|
||||
</a>
|
||||
<a href="/docs/forest-3" target="_top">
|
||||
<circle cx="150" cy="125" r="32" fill="url(#glow-Forest)"/>
|
||||
<circle cx="150" cy="125" r="18" fill="#2e7d32cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="174" y="129" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Forest 3</text>
|
||||
</a>
|
||||
<a href="/docs/forest-4" target="_top">
|
||||
<circle cx="420" cy="100" r="32" fill="url(#glow-Forest)"/>
|
||||
<circle cx="420" cy="100" r="18" fill="#2e7d32cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="444" y="104" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Forest 4</text>
|
||||
</a>
|
||||
<a href="/docs/forest-5" target="_top">
|
||||
<circle cx="470" cy="410" r="32" fill="url(#glow-Forest)"/>
|
||||
<circle cx="470" cy="410" r="18" fill="#2e7d32cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="494" y="414" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Forest 5</text>
|
||||
</a>
|
||||
<a href="/docs/grass-1" target="_top">
|
||||
<circle cx="15" cy="260" r="32" fill="url(#glow-Grass)"/>
|
||||
<circle cx="15" cy="260" r="18" fill="#4caf50cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="39" y="264" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Grass 1</text>
|
||||
<text x="39" y="278" fill="rgba(255,255,255,0.45)" font-family="system-ui,sans-serif" font-size="9" font-style="italic">
|
||||
★ Lone Tree Station</text>
|
||||
</a>
|
||||
<a href="/docs/grass-2" target="_top">
|
||||
<circle cx="150" cy="400" r="32" fill="url(#glow-Grass)"/>
|
||||
<circle cx="150" cy="400" r="18" fill="#4caf50cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="174" y="404" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Grass 2</text>
|
||||
<text x="174" y="418" fill="rgba(255,255,255,0.45)" font-family="system-ui,sans-serif" font-size="9" font-style="italic">
|
||||
★ Spire</text>
|
||||
</a>
|
||||
<a href="/docs/grass-3" target="_top">
|
||||
<circle cx="300" cy="230" r="32" fill="url(#glow-Grass)"/>
|
||||
<circle cx="300" cy="230" r="18" fill="#4caf50cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="324" y="234" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Grass 3</text>
|
||||
</a>
|
||||
<a href="/docs/grass-4" target="_top">
|
||||
<circle cx="510" cy="30" r="32" fill="url(#glow-Grass)"/>
|
||||
<circle cx="510" cy="30" r="18" fill="#4caf50cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="534" y="34" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Grass 4</text>
|
||||
</a>
|
||||
<a href="/docs/grass-5" target="_top">
|
||||
<circle cx="550" cy="290" r="32" fill="url(#glow-Grass)"/>
|
||||
<circle cx="550" cy="290" r="18" fill="#4caf50cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="574" y="294" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Grass 5</text>
|
||||
</a>
|
||||
<a href="/docs/mountain-1" target="_top">
|
||||
<circle cx="20" cy="120" r="32" fill="url(#glow-Mountain)"/>
|
||||
<circle cx="20" cy="120" r="18" fill="#78909ccc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="44" y="124" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Mountain 1</text>
|
||||
</a>
|
||||
<a href="/docs/mountain-2" target="_top">
|
||||
<circle cx="260" cy="110" r="32" fill="url(#glow-Mountain)"/>
|
||||
<circle cx="260" cy="110" r="18" fill="#78909ccc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="284" y="114" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Mountain 2</text>
|
||||
</a>
|
||||
<a href="/docs/mountain-3" target="_top">
|
||||
<circle cx="380" cy="180" r="32" fill="url(#glow-Mountain)"/>
|
||||
<circle cx="380" cy="180" r="18" fill="#78909ccc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="404" y="184" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Mountain 3</text>
|
||||
</a>
|
||||
<a href="/docs/mountain-4" target="_top">
|
||||
<circle cx="370" cy="30" r="32" fill="url(#glow-Mountain)"/>
|
||||
<circle cx="370" cy="30" r="18" fill="#78909ccc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="394" y="34" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Mountain 4</text>
|
||||
</a>
|
||||
<a href="/docs/mountain-5" target="_top">
|
||||
<circle cx="430" cy="330" r="32" fill="url(#glow-Mountain)"/>
|
||||
<circle cx="430" cy="330" r="18" fill="#78909ccc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="454" y="334" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Mountain 5</text>
|
||||
</a>
|
||||
<a href="/docs/wasteland-1" target="_top">
|
||||
<circle cx="560" cy="120" r="32" fill="url(#glow-Wasteland)"/>
|
||||
<circle cx="560" cy="120" r="18" fill="#8d6e63cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="584" y="124" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Wasteland 1</text>
|
||||
</a>
|
||||
<a href="/docs/water-1" target="_top">
|
||||
<circle cx="30" cy="410" r="32" fill="url(#glow-Water)"/>
|
||||
<circle cx="30" cy="410" r="18" fill="#42a5f5cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="54" y="414" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Water 1</text>
|
||||
<text x="54" y="428" fill="rgba(255,255,255,0.45)" font-family="system-ui,sans-serif" font-size="9" font-style="italic">
|
||||
★ Research Station</text>
|
||||
</a>
|
||||
<a href="/docs/water-2" target="_top">
|
||||
<circle cx="140" cy="260" r="32" fill="url(#glow-Water)"/>
|
||||
<circle cx="140" cy="260" r="18" fill="#42a5f5cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="164" y="264" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Water 2</text>
|
||||
<text x="164" y="278" fill="rgba(255,255,255,0.45)" font-family="system-ui,sans-serif" font-size="9" font-style="italic">
|
||||
★ White Sky</text>
|
||||
</a>
|
||||
<a href="/docs/water-3" target="_top">
|
||||
<circle cx="50" cy="50" r="32" fill="url(#glow-Water)"/>
|
||||
<circle cx="50" cy="50" r="18" fill="#42a5f5cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="74" y="54" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Water 3</text>
|
||||
</a>
|
||||
<a href="/docs/water-4" target="_top">
|
||||
<circle cx="640" cy="110" r="32" fill="url(#glow-Water)"/>
|
||||
<circle cx="640" cy="110" r="18" fill="#42a5f5cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="664" y="114" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Water 4</text>
|
||||
</a>
|
||||
<a href="/docs/water-5" target="_top">
|
||||
<circle cx="600" cy="400" r="32" fill="url(#glow-Water)"/>
|
||||
<circle cx="600" cy="400" r="18" fill="#42a5f5cc" stroke="rgba(255,255,255,0.5)" stroke-width="2"/>
|
||||
<text x="624" y="404" fill="rgba(255,255,255,0.9)" font-family="system-ui,sans-serif" font-size="11" font-weight="600">
|
||||
Water 5</text>
|
||||
</a>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,422 @@
|
||||
{
|
||||
"notes": [
|
||||
{
|
||||
"slug": "artificer",
|
||||
"title": "Artificer",
|
||||
"category": "Role"
|
||||
},
|
||||
{
|
||||
"slug": "awareness",
|
||||
"title": "Awareness"
|
||||
},
|
||||
{
|
||||
"slug": "cache-map",
|
||||
"title": "Cache Map",
|
||||
"category": "Gear"
|
||||
},
|
||||
{
|
||||
"slug": "card-library",
|
||||
"title": "Card Library"
|
||||
},
|
||||
{
|
||||
"slug": "concoliator",
|
||||
"title": "Concoliator",
|
||||
"category": "Role"
|
||||
},
|
||||
{
|
||||
"slug": "contents",
|
||||
"title": "Contents"
|
||||
},
|
||||
{
|
||||
"slug": "crisis-markers",
|
||||
"title": "Crisis Markers"
|
||||
},
|
||||
{
|
||||
"slug": "crisis-resolution",
|
||||
"title": "Crisis Resolution"
|
||||
},
|
||||
{
|
||||
"slug": "crisis",
|
||||
"title": "Crisis"
|
||||
},
|
||||
{
|
||||
"slug": "dirt-roads",
|
||||
"title": "Dirt Roads"
|
||||
},
|
||||
{
|
||||
"slug": "dolewood-canoe",
|
||||
"title": "Dolewood Canoe",
|
||||
"category": "Gear"
|
||||
},
|
||||
{
|
||||
"slug": "e03",
|
||||
"title": "E.03",
|
||||
"category": "Event"
|
||||
},
|
||||
{
|
||||
"slug": "ecology",
|
||||
"title": "Ecology"
|
||||
},
|
||||
{
|
||||
"slug": "endeavor-tokens",
|
||||
"title": "Endeavor Tokens"
|
||||
},
|
||||
{
|
||||
"slug": "energy-tokens",
|
||||
"title": "Energy Tokens"
|
||||
},
|
||||
{
|
||||
"slug": "event-cards",
|
||||
"title": "Event Cards"
|
||||
},
|
||||
{
|
||||
"slug": "events",
|
||||
"title": "Events"
|
||||
},
|
||||
{
|
||||
"slug": "explore-phase",
|
||||
"title": "Explore Phase"
|
||||
},
|
||||
{
|
||||
"slug": "explorer",
|
||||
"title": "Explorer",
|
||||
"category": "Role"
|
||||
},
|
||||
{
|
||||
"slug": "ferinodex",
|
||||
"title": "Ferinodex",
|
||||
"category": "Gear"
|
||||
},
|
||||
{
|
||||
"slug": "flora-meeples",
|
||||
"title": "Flora Meeples"
|
||||
},
|
||||
{
|
||||
"slug": "forest-1",
|
||||
"title": "Forest 1",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "forest-2",
|
||||
"title": "Forest 2",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "forest-3",
|
||||
"title": "Forest 3",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "forest-4",
|
||||
"title": "Forest 4",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "forest-5",
|
||||
"title": "Forest 5",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "forest",
|
||||
"title": "Forest"
|
||||
},
|
||||
{
|
||||
"slug": "gauzeblade",
|
||||
"title": "Gauzeblade",
|
||||
"category": "Gear"
|
||||
},
|
||||
{
|
||||
"slug": "gear-cards",
|
||||
"title": "Gear Cards"
|
||||
},
|
||||
{
|
||||
"slug": "grass-1",
|
||||
"title": "Grass 1",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "grass-2",
|
||||
"title": "Grass 2",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "grass-3",
|
||||
"title": "Grass 3",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "grass-4",
|
||||
"title": "Grass 4",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "grass-5",
|
||||
"title": "Grass 5",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "grasslands",
|
||||
"title": "Grasslands"
|
||||
},
|
||||
{
|
||||
"slug": "guide",
|
||||
"title": "Guide",
|
||||
"category": "Role"
|
||||
},
|
||||
{
|
||||
"slug": "hidden-trail-map",
|
||||
"title": "Hidden Trail Map",
|
||||
"category": "Gear"
|
||||
},
|
||||
{
|
||||
"slug": "injury-cards",
|
||||
"title": "Injury Cards"
|
||||
},
|
||||
{
|
||||
"slug": "lake",
|
||||
"title": "Lake"
|
||||
},
|
||||
{
|
||||
"slug": "losing-the-game",
|
||||
"title": "Losing the Game"
|
||||
},
|
||||
{
|
||||
"slug": "market",
|
||||
"title": "Market"
|
||||
},
|
||||
{
|
||||
"slug": "mountain-1",
|
||||
"title": "Mountain 1",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "mountain-2",
|
||||
"title": "Mountain 2",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "mountain-3",
|
||||
"title": "Mountain 3",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "mountain-4",
|
||||
"title": "Mountain 4",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "mountain-5",
|
||||
"title": "Mountain 5",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "mountain",
|
||||
"title": "Mountain"
|
||||
},
|
||||
{
|
||||
"slug": "paratrepsis-whistle",
|
||||
"title": "Paratrepsis Whistle",
|
||||
"category": "Gear"
|
||||
},
|
||||
{
|
||||
"slug": "paved-roads",
|
||||
"title": "Paved Roads"
|
||||
},
|
||||
{
|
||||
"slug": "perfect-day-1",
|
||||
"title": "Perfect Day 1",
|
||||
"category": "Event"
|
||||
},
|
||||
{
|
||||
"slug": "phonoscopic-headset",
|
||||
"title": "Phonoscopic Headset",
|
||||
"category": "Gear"
|
||||
},
|
||||
{
|
||||
"slug": "player-boards",
|
||||
"title": "Player Boards"
|
||||
},
|
||||
{
|
||||
"slug": "player-meeples",
|
||||
"title": "Player Meeples"
|
||||
},
|
||||
{
|
||||
"slug": "predator-meeples",
|
||||
"title": "Predator Meeples"
|
||||
},
|
||||
{
|
||||
"slug": "prepare-phase",
|
||||
"title": "Prepare Phase"
|
||||
},
|
||||
{
|
||||
"slug": "press-on",
|
||||
"title": "Press On"
|
||||
},
|
||||
{
|
||||
"slug": "prey-meeples",
|
||||
"title": "Prey Meeples"
|
||||
},
|
||||
{
|
||||
"slug": "progress",
|
||||
"title": "Progress"
|
||||
},
|
||||
{
|
||||
"slug": "ranger-badges",
|
||||
"title": "Ranger Badges"
|
||||
},
|
||||
{
|
||||
"slug": "ranger-meeples",
|
||||
"title": "Ranger Meeples"
|
||||
},
|
||||
{
|
||||
"slug": "research-station",
|
||||
"title": "Research Station"
|
||||
},
|
||||
{
|
||||
"slug": "rest",
|
||||
"title": "Rest"
|
||||
},
|
||||
{
|
||||
"slug": "role-cards",
|
||||
"title": "Role Cards"
|
||||
},
|
||||
{
|
||||
"slug": "round",
|
||||
"title": "Round"
|
||||
},
|
||||
{
|
||||
"slug": "ruins-map",
|
||||
"title": "Ruins Map",
|
||||
"category": "Gear"
|
||||
},
|
||||
{
|
||||
"slug": "scout",
|
||||
"title": "Scout"
|
||||
},
|
||||
{
|
||||
"slug": "shaper",
|
||||
"title": "Shaper",
|
||||
"category": "Role"
|
||||
},
|
||||
{
|
||||
"slug": "shepard",
|
||||
"title": "Shepard",
|
||||
"category": "Role"
|
||||
},
|
||||
{
|
||||
"slug": "story",
|
||||
"title": "Story"
|
||||
},
|
||||
{
|
||||
"slug": "supply",
|
||||
"title": "Supply"
|
||||
},
|
||||
{
|
||||
"slug": "terrain-2",
|
||||
"title": "Terrain 2",
|
||||
"category": "Terrain"
|
||||
},
|
||||
{
|
||||
"slug": "terrain-3",
|
||||
"title": "Terrain 3",
|
||||
"category": "Terrain"
|
||||
},
|
||||
{
|
||||
"slug": "terrain-cards",
|
||||
"title": "Terrain Cards"
|
||||
},
|
||||
{
|
||||
"slug": "terrain",
|
||||
"title": "Terrain",
|
||||
"category": "Terrain"
|
||||
},
|
||||
{
|
||||
"slug": "totem-of-the-irix",
|
||||
"title": "Totem of the Irix",
|
||||
"category": "Gear"
|
||||
},
|
||||
{
|
||||
"slug": "trade",
|
||||
"title": "Trade"
|
||||
},
|
||||
{
|
||||
"slug": "trader",
|
||||
"title": "Trader",
|
||||
"category": "Role"
|
||||
},
|
||||
{
|
||||
"slug": "trail-markers",
|
||||
"title": "Trail Markers"
|
||||
},
|
||||
{
|
||||
"slug": "travel-phase",
|
||||
"title": "Travel Phase"
|
||||
},
|
||||
{
|
||||
"slug": "traverse",
|
||||
"title": "Traverse"
|
||||
},
|
||||
{
|
||||
"slug": "unmaintained-roads",
|
||||
"title": "Unmaintained Roads"
|
||||
},
|
||||
{
|
||||
"slug": "valley",
|
||||
"title": "Valley"
|
||||
},
|
||||
{
|
||||
"slug": "victory-condition",
|
||||
"title": "Victory Condition"
|
||||
},
|
||||
{
|
||||
"slug": "wasteland-1",
|
||||
"title": "Wasteland 1",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "wasteland",
|
||||
"title": "Wasteland"
|
||||
},
|
||||
{
|
||||
"slug": "water-1",
|
||||
"title": "Water 1",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "water-2",
|
||||
"title": "Water 2",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "water-3",
|
||||
"title": "Water 3",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "water-4",
|
||||
"title": "Water 4",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "water-5",
|
||||
"title": "Water 5",
|
||||
"category": "Region"
|
||||
},
|
||||
{
|
||||
"slug": "weather",
|
||||
"title": "Weather"
|
||||
},
|
||||
{
|
||||
"slug": "white-sky",
|
||||
"title": "White Sky"
|
||||
},
|
||||
{
|
||||
"slug": "xp-cubes",
|
||||
"title": "XP Cubes"
|
||||
},
|
||||
{
|
||||
"slug": "xp",
|
||||
"title": "XP"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Role
|
||||
Role Effect: Can have an additional gear and can spend focus to ready that gear or add a stored item from the supply.
|
||||
Role Veteran Effect: Can have two additional gear and can spend focus to ready that gear or add a stored item from the supply
|
||||
Unknown: "4"
|
||||
---
|
||||
@@ -0,0 +1 @@
|
||||
Green energy.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: Gear
|
||||
Cost: 1
|
||||
Effect: "Travel: Discard this gear and spend 2 [[Progress]] while in this region to gain 1 [[Tool]]."
|
||||
Location: Anywhere
|
||||
Gear Category: Tool
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
count: 380
|
||||
---
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Role
|
||||
Role Effect: Can have any number of companions with you. Can exchange companions with any Ranger nearby during the prepare phase.
|
||||
Role Veteran Effect: Ignore the effects of collateral damage. Can exchange companions with any Ranger anywhere during the prepare phase.
|
||||
Unknown: "4"
|
||||
---
|
||||
@@ -0,0 +1,20 @@
|
||||
| Item | Count |
|
||||
| -------------------- | ----- |
|
||||
| [[Terrain Cards]] | 198 |
|
||||
| [[Card Library]] | 380 |
|
||||
| [[Event Cards]] | 40 |
|
||||
| [[Gear Cards]] | 50 |
|
||||
| [[Injury Cards]] | 20 |
|
||||
| [[Endeavor Tokens]] | 48 |
|
||||
| [[Energy Tokens]] | 80 |
|
||||
| [[Crisis Markers]] | 7 |
|
||||
| [[Flora Meeples]] | 40 |
|
||||
| [[Prey Meeples]] | 40 |
|
||||
| [[Predator Meeples]] | 40 |
|
||||
| [[XP Cubes]] | 5 |
|
||||
| [[Ranger Meeples]] | 20 |
|
||||
| [[Player Boards]] | 5 |
|
||||
| [[Player Meeples]] | 5 |
|
||||
| [[Role Cards]] | 10 |
|
||||
| [[Trail Markers]] | 10 |
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
When the third Ranger is placed on a crisis. Resolve it, retire 1 of the Rangers, and roll the risk die to determine which of the others retire as well.
|
||||
@@ -0,0 +1,2 @@
|
||||
Find and encounter a crisis card to attempt to solve the crisis yourself.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
2 Progress + Ecology Meeples increases travel cost.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: Gear
|
||||
Cost: 4
|
||||
Gear Category: Tool
|
||||
Effect: "Boat. Travel: You need 1 less Progress to place trail markers in water regions."
|
||||
Location: White Sky
|
||||
---
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
category: Event
|
||||
isCrisis: true
|
||||
Event Type: Mountain
|
||||
Activates Ecology:
|
||||
Lake Ecology:
|
||||
- "[[Prey Meeples]]"
|
||||
Description: Some of the best experience in life is hard-earned by taking foolish risks, and a few things are riskier than going into the mountains during a thunder-storm.
|
||||
---
|
||||
Each Ranger can choose to suffer 2 fatigue to gain 2 XP.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
count: 40
|
||||
---
|
||||
Draw and resolve on event card at the start of each [[Round]].
|
||||
|
||||
Event cards spawn [[Crisis]], activate the [[Ecology]], and represent the [[Story]] and [[Weather]] affecting the [[Valley]].
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[[Sun]]
|
||||
[[Mountain]]
|
||||
[[Seal]]
|
||||
@@ -0,0 +1,3 @@
|
||||
Draw terrain cards as [[Progress]] until you encounter one.
|
||||
|
||||
![[Press On]]
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
category: Role
|
||||
Awareness: 2
|
||||
Fitnesses: 2
|
||||
Knowledge: 1
|
||||
Spirit: 1
|
||||
Unknown: "4"
|
||||
Role Effect: Ignores predators when suffering fatigue to continue exploring.
|
||||
Role Veteran Effect: All nearby Rangers ignore predators when suffering fatigue to continue exploring.
|
||||
---
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: Gear
|
||||
Cost: 2
|
||||
Gear Category: Tool
|
||||
Effect: "Explore: Once per day, use in the place of 1[[Focus]]."
|
||||
Location: Anywhere
|
||||
---
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Grass 1]]"
|
||||
- "[[Grass 2]]"
|
||||
- "[[Water 1]]"
|
||||
- "[[Forest 2]]"
|
||||
X: 60
|
||||
Y: 330
|
||||
---
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Grass 2]]"
|
||||
- "[[Water 2]]"
|
||||
- "[[Forest 1]]"
|
||||
X: 250
|
||||
Y: 370
|
||||
---
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Water 2]]"
|
||||
- "[[Water 3]]"
|
||||
X: 150
|
||||
Y: 125
|
||||
---
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Grass 4]]"
|
||||
- "[[Wasteland 1]]"
|
||||
- "[[Grass 5]]"
|
||||
Y: 100
|
||||
X: 420
|
||||
---
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Water 5]]"
|
||||
Y: 410
|
||||
X: 470
|
||||
---
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: Gear
|
||||
Cost: 5
|
||||
Gear Category: Weapon
|
||||
Effect: "Explore: Spend 2 [[Fitness]] before encountering a predator or prey to store it. The stored being can be traded as a gear of value 2."
|
||||
Location: "[[Lone Tree Station]]"
|
||||
---
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Mountain 1]]"
|
||||
- "[[Water 2]]"
|
||||
- "[[Forest 1]]"
|
||||
X: 15
|
||||
Y: 260
|
||||
Landmarks:
|
||||
- "[[Lone Tree Station]]"
|
||||
---
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Forest 1]]"
|
||||
- "[[Forest 2]]"
|
||||
X: 150
|
||||
Y: 400
|
||||
Landmarks:
|
||||
- "[[Spire]]"
|
||||
---
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Water 2]]"
|
||||
- "[[Mountain 2]]"
|
||||
- "[[Mountain 3]]"
|
||||
X: 300
|
||||
Y: 230
|
||||
---
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Mountain 4]]"
|
||||
- "[[Wasteland 1]]"
|
||||
- "[[Grass 4]]"
|
||||
Y: 30
|
||||
X: 510
|
||||
---
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Mountain 5]]"
|
||||
- "[[Water 5]]"
|
||||
- "[[Wasteland 1]]"
|
||||
- "[[Forest 4]]"
|
||||
Y: 290
|
||||
X: 550
|
||||
---
|
||||
@@ -0,0 +1,2 @@
|
||||
Default region that has [[Lone Tree Station]].
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
category: Role
|
||||
Role Effect: Can help nearby Rangers.
|
||||
Role Veteran Effect: Can help any Ranger anywhere.
|
||||
Awareness: 2
|
||||
Fitnesses: 2
|
||||
Knowledge: 2
|
||||
Spirit: 2
|
||||
Unknown: "5"
|
||||
---
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: Gear
|
||||
Cost: 3
|
||||
Gear Category: Tool
|
||||
Effect: "Prepare: Discard to travel to a nearby region."
|
||||
Location: Anywhere
|
||||
---
|
||||
@@ -0,0 +1 @@
|
||||
If you ever need to deploy a [[Ranger Meeples]] from [[Lone Tree Station]] but can't you lose the game.
|
||||
@@ -0,0 +1,2 @@
|
||||
Cards you can buy. Some cards need you to be at a specific location to buy them.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Grass 1]]"
|
||||
- "[[Water 3]]"
|
||||
- "[[Forest 3]]"
|
||||
X: 20
|
||||
Y: 120
|
||||
---
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Water 3]]"
|
||||
- "[[Forest 3]]"
|
||||
X: 260
|
||||
Y: 110
|
||||
---
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Mountain 5]]"
|
||||
- "[[Forest 4]]"
|
||||
- "[[Grass 3]]"
|
||||
Y: 180
|
||||
X: 380
|
||||
---
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Forest 4]]"
|
||||
- "[[Grass 4]]"
|
||||
Y: 30
|
||||
X: 370
|
||||
---
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
category: Region
|
||||
Connections:
|
||||
- "[[Grass 5]]"
|
||||
- "[[Mountain 3]]"
|
||||
Y: 330
|
||||
X: 430
|
||||
---
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: Gear
|
||||
Cost: 6
|
||||
Gear Category: Tool
|
||||
Effect: "Explore: You can spend 1 [[Focus]] to prevent suffering 1 injury."
|
||||
Location: "[[Lone Tree Station]]"
|
||||
---
|
||||
@@ -0,0 +1 @@
|
||||
1 Progress + Ecology Meeples increases travel cost.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
category: Event
|
||||
isCrisis: true
|
||||
Event Type: Mountain
|
||||
Forest Ecology:
|
||||
- "[[Prey Meeples]]"
|
||||
Lake Ecology:
|
||||
- "[[Prey Meeples]]"
|
||||
Grass Ecology:
|
||||
Mountain Ecology:
|
||||
Wasteland Ecology:
|
||||
Effect: In each region with 3 or more prey, they stampede! Each Ranger in that region suffers 1 injury, then distribute the prey nearby. Shuffle and add 5 random cards from E to the top of the event deck.
|
||||
Description: A massive crack of thunder heralds the approach of a storm rolling into the Valley. At the sound of the deafening boom, herbs of startled animals scatter and run for cover.
|
||||
---
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: Gear
|
||||
Cost: 2
|
||||
Gear Category: Garment
|
||||
Effect: "Explore: Once per day, use in the place of 1 [[Awareness]]"
|
||||
Location:
|
||||
---
|
||||
@@ -0,0 +1 @@
|
||||
Containers helper notes around player reminder rules for a player.
|
||||
@@ -0,0 +1,9 @@
|
||||
Spend energy to prepare for your day, or save it to explore.
|
||||
|
||||
![[Scout]]
|
||||
|
||||
![[Traverse]]
|
||||
|
||||
![[Rest]]
|
||||
|
||||
![[Trade]]
|
||||
@@ -0,0 +1 @@
|
||||
When your explore step would end, you may suffer 1 fatigue (plus 1 for each predator) to continue exploring.
|
||||
@@ -0,0 +1 @@
|
||||
Progress represents how far you come in order to travel on.
|
||||
@@ -0,0 +1,3 @@
|
||||
When you run out of Ranger Meeples you lose the game.
|
||||
|
||||
When three Ranger Meeples are on a crisis, they complete it.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
actionType: Prepare
|
||||
energyType: Focus
|
||||
---
|
||||
|
||||
|
||||
Rest to recover fatigue or injuries.
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
| ![[Role Example.png]] | ![[Role Example 2.png]]<br> |
|
||||
| --------------------- | ---------------------------------------- |
|
||||
|
||||
![[Explorer]]
|
||||
|
||||
![[Shepard]]
|
||||
|
||||
![[Artificer]]
|
||||
|
||||
![[Concoliator]]
|
||||
|
||||
![[Trader]]
|
||||
|
||||
![[Guide]]
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: Gear
|
||||
Cost: 2
|
||||
Gear Category: Tool
|
||||
Effect: "Travel: Retire this gear an spend 2[[Progress]] while in this region to gain 1 [[Artifact]]."
|
||||
Location: Anywhere
|
||||
---
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
actionType:
|
||||
- "[[Prepare Phase]]"
|
||||
energyType:
|
||||
- "[[Awareness]]"
|
||||
---
|
||||
Scout terrain cards, putting them on top or bottom.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: Role
|
||||
Unknown: "4"
|
||||
---
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
category: Role
|
||||
Awareness: 2
|
||||
Fitnesses: 1
|
||||
Knowledge: 1
|
||||
Spirit: 2
|
||||
Unknown: "4"
|
||||
Role Effect: You can spend spirit to move an equal number of prey and predator from a nearby region to your region.
|
||||
Role Veteran Effect: You can also move them out of your region to a nearby one.
|
||||
---
|
||||
@@ -0,0 +1 @@
|
||||
The deck of gear. Things that can get something, like a tool, and get the top most tool from the supply.
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
category: Terrain
|
||||
Description: You come across a squat building sitting in the shadow of [[Lone Tree Station]]. Other Rangers are coming and going, checking in with an older man who hunches over a map of the Valley, his impressively-wide hatbrim shading almost the entire table as he draws lines on the map, sending out Rangers to blaze new trails after a season of growth.
|
||||
---
|
||||
1 [[Spirit]] Ask him to show you marked trails. Place one of your [[Trail Markers]] on any region.
|
||||
|
||||
2 [[Awareness]] Offer to take over mapping.
|
||||
|
||||
Walk by the outpost.
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
category: Terrain
|
||||
Description: The shadow of the giga-redwood is a flurry of activity as Rangers go about their assigned duties and exchange stories with the people of the Valley.
|
||||
---
|
||||
1 [[Spirit]] Listen to stories about goings-on throughout the Valley.
|
||||
|
||||
1 [[Focus]] Recommend new duties to a Range out on the field.
|
||||
|
||||
1 [[Awareness]] Request help with a crisis.
|
||||
|
||||
X Leave everyone to their jobs.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
count: "198"
|
||||
---
|
||||
Cards you draw when exploring the matching region.
|
||||
|
||||
Each Terrain type appears to be spread out across 5 regions in the map, the exception being [[Wasteland]] which is just one location to the west.
|
||||
|
||||
# Categories
|
||||
![[Lake]]
|
||||
![[Grasslands]]
|
||||
![[Forest]]
|
||||
![[Mountain]]
|
||||
![[Wasteland]]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
category: Terrain
|
||||
Description: You climb to the top of Lone Tree Station to get the vantage from the circular platforms of Topside Mast. There, you see Ben Amon working on the Swift. It hasn't been functioning perfectly lately, but the artificer keeps it working enough to send Rangers out to deal with crises that arise.
|
||||
---
|
||||
1 [[Spirit]] Send a Ranger on the Swift to a crisis.
|
||||
|
||||
2 [[Focus]] Board the Swift yourself. Travel to any region.
|
||||
|
||||
|
||||
X Climb down out of [[Lone Tree Station]].
|
||||
|
||||
If Mora Orlin is with you, offer her help repairing the Swift.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
category: Gear
|
||||
Cost: 2
|
||||
Gear Category: Consumable
|
||||
Effect: "Explore: Discard to help a Ranger anywhere."
|
||||
Location: Anywhere
|
||||
---
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
actionType: Prepare
|
||||
energyType: Spirit
|
||||
---
|
||||
|
||||
|
||||
Trade to acquire gear from the market (or exchange).
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
category: Role
|
||||
Awareness: 1
|
||||
Fitnesses: 1
|
||||
Knowledge: 2
|
||||
Spirit: 2
|
||||
Unknown: "4"
|
||||
Role Effect: All gear counts as one higher when trading. And can exchange gear with any nearby Ranger.
|
||||
Role Veteran Effect: All of your gear counts as two higher when trading. And can exchange gear with any Ranger anywhere.
|
||||
---
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user