mplemented the public navigation and SEO coverage pass.
This commit is contained in:
parent
bd5bf07906
commit
19d9bc4bb1
@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using PlotLine.Services;
|
||||
using PlotLine.ViewModels;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace PlotLine.Controllers;
|
||||
|
||||
@ -11,7 +10,7 @@ namespace PlotLine.Controllers;
|
||||
[AllowAnonymous]
|
||||
public sealed class HelpController(IHelpService help, IHelpMarkdownRenderer markdown, IConfiguration configuration) : Controller
|
||||
{
|
||||
[HttpGet("")]
|
||||
[HttpGet("", Name = PublicRouteNames.Help)]
|
||||
public IActionResult Index(string? q)
|
||||
{
|
||||
if (!IsCanonicalPath("/help"))
|
||||
@ -19,11 +18,11 @@ public sealed class HelpController(IHelpService help, IHelpMarkdownRenderer mark
|
||||
return RedirectPermanent(PreserveQuery("/help"));
|
||||
}
|
||||
|
||||
var canonicalUrl = BuildAbsoluteUrl("/help");
|
||||
var canonicalUrl = PublicSeo.AbsoluteUrl(configuration, "/help");
|
||||
ViewData["CanonicalUrl"] = canonicalUrl;
|
||||
ViewData["StructuredData"] = new[]
|
||||
{
|
||||
JsonLd(new Dictionary<string, object?>
|
||||
PublicSeo.JsonLd(new Dictionary<string, object?>
|
||||
{
|
||||
["@context"] = "https://schema.org",
|
||||
["@type"] = "CollectionPage",
|
||||
@ -31,7 +30,7 @@ public sealed class HelpController(IHelpService help, IHelpMarkdownRenderer mark
|
||||
["url"] = canonicalUrl,
|
||||
["description"] = "Find practical guidance for planning, tracking, and shaping your story in PlotDirector."
|
||||
}),
|
||||
BuildBreadcrumbJsonLd(
|
||||
PublicSeo.BreadcrumbJsonLd(
|
||||
("Help", canonicalUrl))
|
||||
};
|
||||
var articles = help.GetAllArticles();
|
||||
@ -52,7 +51,7 @@ public sealed class HelpController(IHelpService help, IHelpMarkdownRenderer mark
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{categorySlug}")]
|
||||
[HttpGet("{categorySlug}", Name = PublicRouteNames.HelpCategory)]
|
||||
public IActionResult Category(string categorySlug)
|
||||
{
|
||||
var category = help.GetCategoryBySlug(categorySlug);
|
||||
@ -68,11 +67,11 @@ public sealed class HelpController(IHelpService help, IHelpMarkdownRenderer mark
|
||||
}
|
||||
|
||||
var articles = help.GetByCategorySlug(categorySlug);
|
||||
var canonicalUrl = BuildAbsoluteUrl(canonicalPath);
|
||||
var canonicalUrl = PublicSeo.AbsoluteUrl(configuration, canonicalPath);
|
||||
ViewData["CanonicalUrl"] = canonicalUrl;
|
||||
ViewData["StructuredData"] = new[]
|
||||
{
|
||||
JsonLd(new Dictionary<string, object?>
|
||||
PublicSeo.JsonLd(new Dictionary<string, object?>
|
||||
{
|
||||
["@context"] = "https://schema.org",
|
||||
["@type"] = "CollectionPage",
|
||||
@ -80,8 +79,8 @@ public sealed class HelpController(IHelpService help, IHelpMarkdownRenderer mark
|
||||
["url"] = canonicalUrl,
|
||||
["description"] = $"Help articles about {category} in PlotDirector."
|
||||
}),
|
||||
BuildBreadcrumbJsonLd(
|
||||
("Help", BuildAbsoluteUrl("/help")),
|
||||
PublicSeo.BreadcrumbJsonLd(
|
||||
("Help", PublicSeo.AbsoluteUrl(configuration, "/help")),
|
||||
(category, canonicalUrl))
|
||||
};
|
||||
return View(new HelpCategoryArticlesViewModel
|
||||
@ -92,7 +91,7 @@ public sealed class HelpController(IHelpService help, IHelpMarkdownRenderer mark
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("{categorySlug}/{articleSlug}")]
|
||||
[HttpGet("{categorySlug}/{articleSlug}", Name = PublicRouteNames.HelpArticle)]
|
||||
public IActionResult Article(string categorySlug, string articleSlug)
|
||||
{
|
||||
var article = help.GetBySlugs(categorySlug, articleSlug);
|
||||
@ -106,11 +105,11 @@ public sealed class HelpController(IHelpService help, IHelpMarkdownRenderer mark
|
||||
return RedirectPermanent(article.Url);
|
||||
}
|
||||
|
||||
var canonicalUrl = BuildAbsoluteUrl(article.Url);
|
||||
var canonicalUrl = PublicSeo.AbsoluteUrl(configuration, article.Url);
|
||||
ViewData["CanonicalUrl"] = canonicalUrl;
|
||||
ViewData["StructuredData"] = new[]
|
||||
{
|
||||
JsonLd(new Dictionary<string, object?>
|
||||
PublicSeo.JsonLd(new Dictionary<string, object?>
|
||||
{
|
||||
["@context"] = "https://schema.org",
|
||||
["@type"] = "TechArticle",
|
||||
@ -120,9 +119,9 @@ public sealed class HelpController(IHelpService help, IHelpMarkdownRenderer mark
|
||||
["description"] = string.IsNullOrWhiteSpace(article.QuickSummary) ? null : article.QuickSummary,
|
||||
["dateModified"] = article.LastModifiedUtc == default ? null : article.LastModifiedUtc.ToString("O")
|
||||
}),
|
||||
BuildBreadcrumbJsonLd(
|
||||
("Help", BuildAbsoluteUrl("/help")),
|
||||
(article.Category, BuildAbsoluteUrl(article.CategoryUrl)),
|
||||
PublicSeo.BreadcrumbJsonLd(
|
||||
("Help", PublicSeo.AbsoluteUrl(configuration, "/help")),
|
||||
(article.Category, PublicSeo.AbsoluteUrl(configuration, article.CategoryUrl)),
|
||||
(article.Title, canonicalUrl))
|
||||
};
|
||||
return View(new HelpCentreArticleViewModel
|
||||
@ -160,34 +159,4 @@ public sealed class HelpController(IHelpService help, IHelpMarkdownRenderer mark
|
||||
? path
|
||||
: $"{path}{HttpContext.Request.QueryString.Value}";
|
||||
|
||||
private string BuildAbsoluteUrl(string path)
|
||||
{
|
||||
var baseUrl = configuration["Site:PublicBaseUrl"]?.TrimEnd('/');
|
||||
if (string.IsNullOrWhiteSpace(baseUrl))
|
||||
{
|
||||
baseUrl = "https://plotdirector.com";
|
||||
}
|
||||
|
||||
return $"{baseUrl}{path}";
|
||||
}
|
||||
|
||||
private static string BuildBreadcrumbJsonLd(params (string Name, string Url)[] items) =>
|
||||
JsonLd(new Dictionary<string, object?>
|
||||
{
|
||||
["@context"] = "https://schema.org",
|
||||
["@type"] = "BreadcrumbList",
|
||||
["itemListElement"] = items.Select((item, index) => new Dictionary<string, object?>
|
||||
{
|
||||
["@type"] = "ListItem",
|
||||
["position"] = index + 1,
|
||||
["name"] = item.Name,
|
||||
["item"] = item.Url
|
||||
})
|
||||
});
|
||||
|
||||
private static string JsonLd(object value) =>
|
||||
JsonSerializer.Serialize(value, new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,17 +1,35 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
using PlotLine.Models;
|
||||
|
||||
using PlotLine.Services;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace PlotLine.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public class HomeController : Controller
|
||||
public class HomeController(IConfiguration configuration) : Controller
|
||||
{
|
||||
[HttpGet("/", Name = PublicRouteNames.Home)]
|
||||
public IActionResult Index()
|
||||
{
|
||||
const string description = "PlotDirector helps authors organise characters, plot threads, timelines, story assets, relationships, and continuity so they can finish complex novels with confidence.";
|
||||
var canonicalUrl = PublicSeo.AbsoluteUrl(configuration, "/");
|
||||
ViewData["SeoTitle"] = "PlotDirector | Novel Planning Software for Authors";
|
||||
ViewData["SeoDescription"] = description;
|
||||
ViewData["SeoKeywords"] = "novel planning software, writing software, story planning app, author tools, plot tracking, character tracking, story timeline, novel organisation, fiction writing software, series writing software, continuity tracking, plot threads, story bible";
|
||||
ViewData["CanonicalUrl"] = canonicalUrl;
|
||||
ViewData["StructuredData"] = new[]
|
||||
{
|
||||
PublicSeo.JsonLd(new Dictionary<string, object?>
|
||||
{
|
||||
["@context"] = "https://schema.org",
|
||||
["@type"] = "WebSite",
|
||||
["name"] = "PlotDirector",
|
||||
["url"] = PublicSeo.BaseUrl(configuration),
|
||||
["description"] = "Novel planning and story organisation software for authors."
|
||||
}),
|
||||
PublicSeo.WebPageJsonLd(configuration, "WebPage", "PlotDirector", "/", description)
|
||||
};
|
||||
return View();
|
||||
}
|
||||
|
||||
|
||||
@ -12,11 +12,22 @@ public sealed class PricingController(
|
||||
ICurrentUserService currentUser,
|
||||
IUserRepository users,
|
||||
ISubscriptionService subscriptions,
|
||||
IStripeBillingService stripeBilling) : Controller
|
||||
IStripeBillingService stripeBilling,
|
||||
IConfiguration configuration) : Controller
|
||||
{
|
||||
[HttpGet]
|
||||
[HttpGet("pricing", Name = PublicRouteNames.Pricing)]
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
const string description = "Compare PlotDirector plans and choose the right subscription for organising your novel, series, characters, plot threads, timelines, and story details.";
|
||||
ViewData["SeoTitle"] = "Pricing | PlotDirector";
|
||||
ViewData["SeoDescription"] = description;
|
||||
ViewData["SeoKeywords"] = "novel planning software, writing software, story planning app, author tools, plot tracking, character tracking, story timeline, novel organisation, fiction writing software, series writing software, continuity tracking, plot threads, story bible";
|
||||
ViewData["CanonicalUrl"] = PublicSeo.AbsoluteUrl(configuration, "/pricing");
|
||||
ViewData["StructuredData"] = new[]
|
||||
{
|
||||
PublicSeo.WebPageJsonLd(configuration, "WebPage", "Pricing", "/pricing", description)
|
||||
};
|
||||
|
||||
UserSubscriptionDetail? currentSubscription = null;
|
||||
if (currentUser.UserId is int userId)
|
||||
{
|
||||
@ -47,7 +58,7 @@ public sealed class PricingController(
|
||||
{
|
||||
if (!currentUser.UserId.HasValue)
|
||||
{
|
||||
var returnUrl = Url.Action(nameof(Checkout), "Pricing", new { id, billingInterval }) ?? "/Pricing";
|
||||
var returnUrl = Url.Action(nameof(Checkout), "Pricing", new { id, billingInterval }) ?? "/pricing";
|
||||
return RedirectToAction("Login", "Account", new { returnUrl });
|
||||
}
|
||||
|
||||
|
||||
@ -1,27 +1,77 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlotLine.Services;
|
||||
using PlotLine.ViewModels;
|
||||
|
||||
namespace PlotLine.Controllers;
|
||||
|
||||
[AllowAnonymous]
|
||||
[Route("")]
|
||||
public sealed class PublicController : Controller
|
||||
public sealed class PublicController(IConfiguration configuration) : Controller
|
||||
{
|
||||
private const string SupportEmail = "hello@plotdirector.com";
|
||||
private const string PolicyDate = "June 7, 2026";
|
||||
private const string Keywords = "novel planning software, writing software, story planning app, author tools, plot tracking, character tracking, story timeline, novel organisation, fiction writing software, series writing software, continuity tracking, plot threads, story bible";
|
||||
|
||||
[HttpGet("About")]
|
||||
public IActionResult About() => View(CreateModel());
|
||||
[HttpGet("about", Name = PublicRouteNames.About)]
|
||||
public IActionResult About()
|
||||
{
|
||||
SetSeo(
|
||||
"About PlotDirector | Built for Novelists",
|
||||
"Learn why PlotDirector was created by a novelist to help writers organise complex stories, manage continuity, and finish their books with confidence.",
|
||||
"/about",
|
||||
"AboutPage",
|
||||
"About PlotDirector");
|
||||
return View(CreateModel());
|
||||
}
|
||||
|
||||
[HttpGet("Contact")]
|
||||
public IActionResult Contact() => View(CreateModel());
|
||||
[HttpGet("contact", Name = PublicRouteNames.Contact)]
|
||||
public IActionResult Contact()
|
||||
{
|
||||
SetSeo(
|
||||
"Contact PlotDirector",
|
||||
"Contact PlotDirector for support, questions, feedback, or feature suggestions about our novel planning and story organisation platform.",
|
||||
"/contact",
|
||||
"ContactPage",
|
||||
"Contact PlotDirector");
|
||||
return View(CreateModel());
|
||||
}
|
||||
|
||||
[HttpGet("Privacy")]
|
||||
public IActionResult Privacy() => View(CreateModel());
|
||||
[HttpGet("privacy", Name = PublicRouteNames.Privacy)]
|
||||
public IActionResult Privacy()
|
||||
{
|
||||
SetSeo(
|
||||
"Privacy Policy | PlotDirector",
|
||||
"Read the PlotDirector Privacy Policy to learn how we protect your account information, subscription data, and story content.",
|
||||
"/privacy",
|
||||
"PrivacyPolicy",
|
||||
"Privacy Policy");
|
||||
return View(CreateModel());
|
||||
}
|
||||
|
||||
[HttpGet("Terms")]
|
||||
public IActionResult Terms() => View(CreateModel());
|
||||
[HttpGet("terms", Name = PublicRouteNames.Terms)]
|
||||
public IActionResult Terms()
|
||||
{
|
||||
SetSeo(
|
||||
"Terms of Service | PlotDirector",
|
||||
"Read the PlotDirector Terms of Service for information about subscriptions, user content, acceptable use, and account responsibilities.",
|
||||
"/terms",
|
||||
"WebPage",
|
||||
"Terms of Service");
|
||||
return View(CreateModel());
|
||||
}
|
||||
|
||||
private void SetSeo(string title, string description, string path, string schemaType, string pageName)
|
||||
{
|
||||
ViewData["SeoTitle"] = title;
|
||||
ViewData["SeoDescription"] = description;
|
||||
ViewData["SeoKeywords"] = Keywords;
|
||||
ViewData["CanonicalUrl"] = PublicSeo.AbsoluteUrl(configuration, path);
|
||||
ViewData["StructuredData"] = new[]
|
||||
{
|
||||
PublicSeo.WebPageJsonLd(configuration, schemaType, pageName, path, description)
|
||||
};
|
||||
}
|
||||
|
||||
private static PublicPageViewModel CreateModel() => new()
|
||||
{
|
||||
|
||||
@ -64,7 +64,7 @@ public sealed class SitemapController(IHelpService help, IConfiguration configur
|
||||
foreach (var url in urls)
|
||||
{
|
||||
xml.WriteStartElement("url");
|
||||
xml.WriteElementString("loc", BuildAbsoluteUrl(url.Path));
|
||||
xml.WriteElementString("loc", PublicSeo.AbsoluteUrl(configuration, url.Path));
|
||||
if (url.LastModifiedUtc is DateTime lastModifiedUtc && lastModifiedUtc != default)
|
||||
{
|
||||
xml.WriteElementString("lastmod", lastModifiedUtc.ToString("yyyy-MM-dd"));
|
||||
@ -81,17 +81,6 @@ public sealed class SitemapController(IHelpService help, IConfiguration configur
|
||||
return writer.ToString();
|
||||
}
|
||||
|
||||
private string BuildAbsoluteUrl(string path)
|
||||
{
|
||||
var baseUrl = configuration["Site:PublicBaseUrl"]?.TrimEnd('/');
|
||||
if (string.IsNullOrWhiteSpace(baseUrl))
|
||||
{
|
||||
baseUrl = "https://plotdirector.com";
|
||||
}
|
||||
|
||||
return $"{baseUrl}{path}";
|
||||
}
|
||||
|
||||
private sealed record SitemapUrl(string Path, string ChangeFrequency, string Priority, DateTime? LastModifiedUtc = null);
|
||||
|
||||
private sealed class Utf8StringWriter : StringWriter
|
||||
|
||||
66
PlotLine/Services/PublicSeo.cs
Normal file
66
PlotLine/Services/PublicSeo.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PlotLine.Services;
|
||||
|
||||
public static class PublicRouteNames
|
||||
{
|
||||
public const string Home = "PublicHome";
|
||||
public const string Pricing = "PublicPricing";
|
||||
public const string Help = "PublicHelp";
|
||||
public const string HelpCategory = "PublicHelpCategory";
|
||||
public const string HelpArticle = "PublicHelpArticle";
|
||||
public const string About = "PublicAbout";
|
||||
public const string Contact = "PublicContact";
|
||||
public const string Privacy = "PublicPrivacy";
|
||||
public const string Terms = "PublicTerms";
|
||||
}
|
||||
|
||||
public static class PublicSeo
|
||||
{
|
||||
public const string DefaultBaseUrl = "https://plotdirector.com";
|
||||
|
||||
public static string BaseUrl(IConfiguration configuration)
|
||||
{
|
||||
var baseUrl = configuration["Site:PublicBaseUrl"]?.TrimEnd('/');
|
||||
return string.IsNullOrWhiteSpace(baseUrl) ? DefaultBaseUrl : baseUrl;
|
||||
}
|
||||
|
||||
public static string AbsoluteUrl(IConfiguration configuration, string path) =>
|
||||
$"{BaseUrl(configuration)}{path}";
|
||||
|
||||
public static string JsonLd(object value) =>
|
||||
JsonSerializer.Serialize(value, new JsonSerializerOptions
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
});
|
||||
|
||||
public static string WebPageJsonLd(
|
||||
IConfiguration configuration,
|
||||
string schemaType,
|
||||
string name,
|
||||
string path,
|
||||
string? description = null) =>
|
||||
JsonLd(new Dictionary<string, object?>
|
||||
{
|
||||
["@context"] = "https://schema.org",
|
||||
["@type"] = schemaType,
|
||||
["name"] = name,
|
||||
["url"] = AbsoluteUrl(configuration, path),
|
||||
["description"] = description
|
||||
});
|
||||
|
||||
public static string BreadcrumbJsonLd(params (string Name, string Url)[] items) =>
|
||||
JsonLd(new Dictionary<string, object?>
|
||||
{
|
||||
["@context"] = "https://schema.org",
|
||||
["@type"] = "BreadcrumbList",
|
||||
["itemListElement"] = items.Select((item, index) => new Dictionary<string, object?>
|
||||
{
|
||||
["@type"] = "ListItem",
|
||||
["position"] = index + 1,
|
||||
["name"] = item.Name,
|
||||
["item"] = item.Url
|
||||
})
|
||||
});
|
||||
}
|
||||
@ -1,48 +1,6 @@
|
||||
@{
|
||||
ViewData["Title"] = "Finally finish your novel";
|
||||
ViewData["ShellClass"] = "marketing-shell";
|
||||
ViewData["SeoTitle"] = "PlotDirector | Novel Planning Software for Authors";
|
||||
ViewData["SeoDescription"] = "PlotDirector helps authors organise characters, plot threads, timelines, story assets, relationships, and continuity so they can finish complex novels with confidence.";
|
||||
ViewData["SeoKeywords"] = "novel planning software, writing software, story planning app, author tools, plot tracking, character tracking, story timeline, novel organisation, fiction writing software, series writing software, continuity tracking, plot threads, story bible";
|
||||
ViewData["CanonicalUrl"] = "https://plotdirector.com/";
|
||||
ViewData["StructuredData"] = new[]
|
||||
{
|
||||
"""
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "PlotDirector",
|
||||
"url": "https://plotdirector.com",
|
||||
"description": "Novel planning and story organisation software for authors."
|
||||
}
|
||||
""",
|
||||
"""
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "PlotDirector",
|
||||
"applicationCategory": "WritingApplication",
|
||||
"operatingSystem": "Web",
|
||||
"description": "Novel planning software for authors to organise characters, plot threads, timelines, story assets, relationships, continuity, and series.",
|
||||
"url": "https://plotdirector.com",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"priceCurrency": "GBP",
|
||||
"availability": "https://schema.org/InStock"
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
"name": "PlotDirector",
|
||||
"url": "https://plotdirector.com",
|
||||
"logo": "https://plotdirector.com/android-chrome-512x512.png",
|
||||
"sameAs": []
|
||||
}
|
||||
"""
|
||||
};
|
||||
}
|
||||
|
||||
<article class="marketing-home">
|
||||
|
||||
@ -3,29 +3,6 @@
|
||||
@{
|
||||
ViewData["Title"] = "Pricing";
|
||||
ViewData["ShellClass"] = "marketing-shell";
|
||||
ViewData["SeoTitle"] = "Pricing | PlotDirector";
|
||||
ViewData["SeoDescription"] = "Compare PlotDirector plans and choose the right subscription for organising your novel, series, characters, plot threads, timelines, and story details.";
|
||||
ViewData["SeoKeywords"] = "novel planning software, writing software, story planning app, author tools, plot tracking, character tracking, story timeline, novel organisation, fiction writing software, series writing software, continuity tracking, plot threads, story bible";
|
||||
ViewData["CanonicalUrl"] = "https://plotdirector.com/pricing";
|
||||
ViewData["StructuredData"] = new[]
|
||||
{
|
||||
"""
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "PlotDirector",
|
||||
"applicationCategory": "WritingApplication",
|
||||
"operatingSystem": "Web",
|
||||
"description": "Novel planning software for authors to organise characters, plot threads, timelines, story assets, relationships, continuity, and series.",
|
||||
"url": "https://plotdirector.com",
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"priceCurrency": "GBP",
|
||||
"availability": "https://schema.org/InStock"
|
||||
}
|
||||
}
|
||||
"""
|
||||
};
|
||||
var paidPlans = Model.Plans.Where(x => x.RequiresStripeSubscription).ToList();
|
||||
var suggestedPlanId = paidPlans.Count >= 2 ? paidPlans[paidPlans.Count / 2].SubscriptionLevelID : (int?)null;
|
||||
}
|
||||
|
||||
@ -2,10 +2,6 @@
|
||||
@{
|
||||
ViewData["Title"] = "About";
|
||||
ViewData["ShellClass"] = "marketing-shell";
|
||||
ViewData["SeoTitle"] = "About PlotDirector | Built for Novelists";
|
||||
ViewData["SeoDescription"] = "Learn why PlotDirector was created by a novelist to help writers organise complex stories, manage continuity, and finish their books with confidence.";
|
||||
ViewData["SeoKeywords"] = "novel planning software, writing software, story planning app, author tools, plot tracking, character tracking, story timeline, novel organisation, fiction writing software, series writing software, continuity tracking, plot threads, story bible";
|
||||
ViewData["CanonicalUrl"] = "https://plotdirector.com/about";
|
||||
}
|
||||
|
||||
<article class="marketing-home public-info-page">
|
||||
|
||||
@ -2,10 +2,6 @@
|
||||
@{
|
||||
ViewData["Title"] = "Contact";
|
||||
ViewData["ShellClass"] = "marketing-shell";
|
||||
ViewData["SeoTitle"] = "Contact PlotDirector";
|
||||
ViewData["SeoDescription"] = "Contact PlotDirector for support, questions, feedback, or feature suggestions about our novel planning and story organisation platform.";
|
||||
ViewData["SeoKeywords"] = "novel planning software, writing software, story planning app, author tools, plot tracking, character tracking, story timeline, novel organisation, fiction writing software, series writing software, continuity tracking, plot threads, story bible";
|
||||
ViewData["CanonicalUrl"] = "https://plotdirector.com/contact";
|
||||
}
|
||||
|
||||
<article class="marketing-home public-info-page">
|
||||
|
||||
@ -2,10 +2,6 @@
|
||||
@{
|
||||
ViewData["Title"] = "Privacy Policy";
|
||||
ViewData["ShellClass"] = "marketing-shell";
|
||||
ViewData["SeoTitle"] = "Privacy Policy | PlotDirector";
|
||||
ViewData["SeoDescription"] = "Read the PlotDirector Privacy Policy to learn how we protect your account information, subscription data, and story content.";
|
||||
ViewData["SeoKeywords"] = "novel planning software, writing software, story planning app, author tools, plot tracking, character tracking, story timeline, novel organisation, fiction writing software, series writing software, continuity tracking, plot threads, story bible";
|
||||
ViewData["CanonicalUrl"] = "https://plotdirector.com/privacy";
|
||||
}
|
||||
|
||||
<article class="marketing-home public-info-page">
|
||||
|
||||
@ -2,10 +2,6 @@
|
||||
@{
|
||||
ViewData["Title"] = "Terms of Service";
|
||||
ViewData["ShellClass"] = "marketing-shell";
|
||||
ViewData["SeoTitle"] = "Terms of Service | PlotDirector";
|
||||
ViewData["SeoDescription"] = "Read the PlotDirector Terms of Service for information about subscriptions, user content, acceptable use, and account responsibilities.";
|
||||
ViewData["SeoKeywords"] = "novel planning software, writing software, story planning app, author tools, plot tracking, character tracking, story timeline, novel organisation, fiction writing software, series writing software, continuity tracking, plot threads, story bible";
|
||||
ViewData["CanonicalUrl"] = "https://plotdirector.com/terms";
|
||||
}
|
||||
|
||||
<article class="marketing-home public-info-page">
|
||||
|
||||
@ -105,7 +105,7 @@
|
||||
<header>
|
||||
<nav class="navbar navbar-expand-sm navbar-toggleable-sm border-bottom plotline-nav mb-3">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand fw-semibold" asp-area="" asp-controller="Home" asp-action="Index">PlotDirector</a>
|
||||
<a class="navbar-brand fw-semibold" asp-route="@PublicRouteNames.Home">PlotDirector</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
|
||||
aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
@ -124,7 +124,7 @@
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="Imports" asp-action="Index">Import</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" href="/help">Help</a>
|
||||
<a class="nav-link text-dark" asp-route="@PublicRouteNames.Help">Help</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" asp-area="" asp-controller="FeatureRequests" asp-action="Index">
|
||||
@ -151,13 +151,13 @@
|
||||
else
|
||||
{
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" href="/">Home</a>
|
||||
<a class="nav-link text-dark" asp-route="@PublicRouteNames.Home">Home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" href="/pricing">Pricing</a>
|
||||
<a class="nav-link text-dark" asp-route="@PublicRouteNames.Pricing">Pricing</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-dark" href="/help">Help</a>
|
||||
<a class="nav-link text-dark" asp-route="@PublicRouteNames.Help">Help</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
@ -245,14 +245,15 @@
|
||||
{
|
||||
<footer class="public-footer" aria-label="Public site footer">
|
||||
<div class="marketing-container public-footer__inner">
|
||||
<a class="public-footer__brand" asp-controller="Home" asp-action="Index">PlotDirector</a>
|
||||
<a class="public-footer__brand" asp-route="@PublicRouteNames.Home">PlotDirector</a>
|
||||
<nav class="public-footer__nav" aria-label="Footer navigation">
|
||||
<a asp-controller="Home" asp-action="Index">Home</a>
|
||||
<a asp-controller="Public" asp-action="About">About</a>
|
||||
<a asp-controller="Pricing" asp-action="Index">Pricing</a>
|
||||
<a asp-controller="Public" asp-action="Contact">Contact</a>
|
||||
<a asp-controller="Public" asp-action="Privacy">Privacy Policy</a>
|
||||
<a asp-controller="Public" asp-action="Terms">Terms of Service</a>
|
||||
<a asp-route="@PublicRouteNames.Home">Home</a>
|
||||
<a asp-route="@PublicRouteNames.About">About</a>
|
||||
<a asp-route="@PublicRouteNames.Pricing">Pricing</a>
|
||||
<a asp-route="@PublicRouteNames.Help">Help</a>
|
||||
<a asp-route="@PublicRouteNames.Contact">Contact</a>
|
||||
<a asp-route="@PublicRouteNames.Privacy">Privacy Policy</a>
|
||||
<a asp-route="@PublicRouteNames.Terms">Terms of Service</a>
|
||||
</nav>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@ -19,6 +19,9 @@
|
||||
"Storage": {
|
||||
"UploadsRoot": ""
|
||||
},
|
||||
"Site": {
|
||||
"PublicBaseUrl": "https://plotdirector.com"
|
||||
},
|
||||
"Admin": {
|
||||
"AllowedEmails": ["nickbeckley5@gmail.com"]
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user