Add project files.
25
CatherineLynwood.sln
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.11.35327.3
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CatherineLynwood", "CatherineLynwood\CatherineLynwood.csproj", "{7943D8DC-5C2F-4223-A00D-4A0A1050C163}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7943D8DC-5C2F-4223-A00D-4A0A1050C163}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7943D8DC-5C2F-4223-A00D-4A0A1050C163}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7943D8DC-5C2F-4223-A00D-4A0A1050C163}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7943D8DC-5C2F-4223-A00D-4A0A1050C163}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BCFC7C18-6734-4281-B2C2-F0A8559F4F56}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
36
CatherineLynwood/CatherineLynwood.csproj
Normal file
@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Remove="bundleconfig.json" />
|
||||
<Content Update="web.config">
|
||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="bundleconfig.json" />
|
||||
<None Include="node_modules\bootstrap\scss\_custom.scss" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="bootstrap" Version="5.3.3" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
<PackageReference Include="Microsoft.Web.Administration" Version="11.1.0" />
|
||||
<PackageReference Include="SendGrid" Version="9.29.3" />
|
||||
<PackageReference Include="SixLabors.Fonts" Version="2.0.6" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.5" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.4" />
|
||||
<PackageReference Include="SixLabors.ImageSharp.Web" Version="3.1.3" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.10" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="WebMarkupMin.AspNetCore8" Version="2.18.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
33
CatherineLynwood/Components/BlogCommentComponent.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using CatherineLynwood.Models;
|
||||
using CatherineLynwood.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace CatherineLynwood.Components
|
||||
{
|
||||
public class BlogCommentComponent : ViewComponent
|
||||
{
|
||||
private DataAccess _dataAccess;
|
||||
|
||||
public BlogCommentComponent(DataAccess dataAccess)
|
||||
{
|
||||
_dataAccess = dataAccess;
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync(int blogID)
|
||||
{
|
||||
BlogComments blogComments = await _dataAccess.GetBlogCommentsAsync(blogID);
|
||||
|
||||
PreFillContact preFillContact = Request.Cookies["PreFill"] != null ? JsonConvert.DeserializeObject<PreFillContact>(Request.Cookies["PreFill"]) : new PreFillContact();
|
||||
|
||||
blogComments.Name = preFillContact.Name;
|
||||
blogComments.EmailAddress = preFillContact.EmailAddress;
|
||||
blogComments.Age = preFillContact.Age;
|
||||
blogComments.Sex = preFillContact.Sex;
|
||||
|
||||
return View(blogComments);
|
||||
}
|
||||
}
|
||||
}
|
||||
87
CatherineLynwood/Controllers/AskAQuestion.cs
Normal file
@ -0,0 +1,87 @@
|
||||
using CatherineLynwood.Helpers;
|
||||
using CatherineLynwood.Models;
|
||||
using CatherineLynwood.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using SendGrid.Helpers.Mail;
|
||||
|
||||
namespace CatherineLynwood.Controllers
|
||||
{
|
||||
[Route("ask-a-question")]
|
||||
public class AskAQuestion : Controller
|
||||
{
|
||||
private DataAccess _dataAccess;
|
||||
|
||||
public AskAQuestion(DataAccess dataAccess)
|
||||
{
|
||||
_dataAccess = dataAccess;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index(bool showThanks)
|
||||
{
|
||||
Questions questions = await _dataAccess.GetQuestionsAsync();
|
||||
questions.ShowThanks = showThanks;
|
||||
|
||||
PreFillContact preFillContact = Request.Cookies["PreFill"] != null ? JsonConvert.DeserializeObject<PreFillContact>(Request.Cookies["PreFill"]) : new PreFillContact();
|
||||
|
||||
questions.Name = preFillContact.Name;
|
||||
questions.EmailAddress = preFillContact.EmailAddress;
|
||||
questions.Age = preFillContact.Age;
|
||||
questions.Sex = preFillContact.Sex;
|
||||
|
||||
return View(questions);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("ask-a-question")]
|
||||
public async Task<IActionResult> AskQuestion(Question question)
|
||||
{
|
||||
bool showThanks = false;
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
bool visible = await _dataAccess.AddQuestionAsync(question);
|
||||
|
||||
if (!visible)
|
||||
{
|
||||
var to = new EmailAddress(question.EmailAddress, question.Name);
|
||||
var subject = "Thank you from Catherine Lynwood Web Site";
|
||||
var plainTextContent = $"Dear {question.Name},/r/nThank you for taking the time to ask me a question. ";
|
||||
var htmlContent = $"Dear {question.Name},<br>" +
|
||||
$"<p>Thank you for taking the time to ask me a question.</p>" +
|
||||
$"<p>This email is to let you know that all questions are reviewed before appearing on the ask a question page. " +
|
||||
$"This is unfortuantely necessary to avoid spam and possible offensive remarks. " +
|
||||
$"I will endevour to review your question as soon as possible. " +
|
||||
$"Please check back soon.</p>" +
|
||||
$"<p>Catherine Lynwood<br>" +
|
||||
$"Author: The Alpha Flame<br>" +
|
||||
@$"Web: <a href=""https://www.catherinelynwood.com"">www.catherinelynwood.com</a></p>";
|
||||
await SendEmail.Execute(to, subject, plainTextContent, htmlContent);
|
||||
}
|
||||
|
||||
showThanks = true;
|
||||
}
|
||||
|
||||
PreFillContact blogPreFill = new PreFillContact
|
||||
{
|
||||
Name = question.Name,
|
||||
EmailAddress = question.EmailAddress,
|
||||
Age = question.Age,
|
||||
Sex = question.Sex
|
||||
};
|
||||
|
||||
string json = JsonConvert.SerializeObject(blogPreFill);
|
||||
|
||||
CookieOptions cookieOptions = new CookieOptions
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(1)
|
||||
};
|
||||
|
||||
Response.Cookies.Append("PreFill", json, cookieOptions);
|
||||
|
||||
return RedirectToAction("Index", new { showThanks = showThanks });
|
||||
}
|
||||
}
|
||||
}
|
||||
191
CatherineLynwood/Controllers/HomeController.cs
Normal file
@ -0,0 +1,191 @@
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
using CatherineLynwood.Helpers;
|
||||
using CatherineLynwood.Models;
|
||||
using CatherineLynwood.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using SendGrid.Helpers.Mail;
|
||||
|
||||
namespace CatherineLynwood.Controllers
|
||||
{
|
||||
public class HomeController : Controller
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
private DataAccess _dataAccess;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
public HomeController(ILogger<HomeController> logger, DataAccess dataAccess)
|
||||
{
|
||||
_logger = logger;
|
||||
_dataAccess = dataAccess;
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Public Methods
|
||||
|
||||
[Route("about-catherine-lynwood")]
|
||||
public IActionResult AboutCatherineLynwood()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("contact-catherine")]
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ContactCatherine()
|
||||
{
|
||||
Contact contact = new Contact();
|
||||
|
||||
return View(contact);
|
||||
}
|
||||
|
||||
[Route("contact-catherine")]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> ContactCatherine(Contact contact)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
var to = new EmailAddress("catherine@catherinelynwood.com", "Catherine Lynwood");
|
||||
var subject = "Email from Catherine Lynwood Web Site";
|
||||
var plainTextContent = $"Email from: {contact.Name} ({contact.EmailAddress})/r/n{contact.Message}";
|
||||
var htmlContent = $"<strong>Email from: {contact.Name} ({contact.EmailAddress})</strong><br>{contact.Message}";
|
||||
await SendEmail.Execute(to, subject, plainTextContent, htmlContent);
|
||||
|
||||
return RedirectToAction("ThankYou");
|
||||
}
|
||||
|
||||
return View(contact);
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
|
||||
[Route("feed.xml")]
|
||||
[Route("rss.xml")]
|
||||
[Route("rss")]
|
||||
[Route("feed")]
|
||||
public async Task<IActionResult> DownloadRssFeed()
|
||||
{
|
||||
BlogIndex blogIndex = await _dataAccess.GetBlogsAsync(string.Empty);
|
||||
|
||||
XNamespace atom = "http://www.w3.org/2005/Atom"; // Define the Atom namespace
|
||||
|
||||
XDocument rss = new XDocument(
|
||||
new XElement("rss",
|
||||
new XAttribute("version", "2.0"),
|
||||
new XAttribute(XNamespace.Xmlns + "atom", atom), // Add Atom namespace
|
||||
new XElement("channel",
|
||||
new XElement("title", "Catherine Lynwood Blog"),
|
||||
new XElement("link", "https://www.catherinelynwood.com/blog"),
|
||||
new XElement("description", "Latest blog posts from Catherine Lynwood"),
|
||||
|
||||
// Add atom:link with rel="self"
|
||||
new XElement(atom + "link",
|
||||
new XAttribute("href", "https://www.catherinelynwood.com/feed"),
|
||||
new XAttribute("rel", "self"),
|
||||
new XAttribute("type", "application/rss+xml")
|
||||
),
|
||||
|
||||
// Update image link to match channel link
|
||||
new XElement("image",
|
||||
new XElement("url", "https://www.catherinelynwood.com/images/jpg/catherine-lynwood-11-400.jpg"),
|
||||
new XElement("title", "Catherine Lynwood Blog"),
|
||||
new XElement("link", "https://www.catherinelynwood.com/blog") // Updated to match the channel link
|
||||
),
|
||||
|
||||
from blog in blogIndex.Blogs
|
||||
select new XElement("item",
|
||||
new XElement("title", blog.Title),
|
||||
new XElement("link", $"https://www.catherinelynwood.com/the-alpha-flame/blog/{blog.BlogUrl}"),
|
||||
|
||||
// Construct the image URL with "-400.jpg"
|
||||
new XElement("description", new XCData($"<img src='https://www.catherinelynwood.com/{ConvertToJpgUrl(blog.ImageUrl)}' alt='{blog.ImageAlt}' /> {blog.IndexText}")),
|
||||
|
||||
new XElement("pubDate", blog.PublishDate.ToString("R")),
|
||||
new XElement("guid", $"https://www.catherinelynwood.com/the-alpha-flame/blog/{blog.BlogUrl}"),
|
||||
|
||||
// Use the 400px JPEG for the enclosure element
|
||||
new XElement("enclosure",
|
||||
new XAttribute("url", $"https://www.catherinelynwood.com/{ConvertToJpgUrl(blog.ImageUrl)}"),
|
||||
new XAttribute("type", "image/jpeg"),
|
||||
new XAttribute("length", GetFileSizeInBytes(ConvertToJpgUrl(blog.ImageUrl)))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
byte[] rssBytes = Encoding.UTF8.GetBytes(rss.ToString());
|
||||
return File(rssBytes, "application/rss+xml", "rss.xml");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Helper method to get the file size in bytes
|
||||
private long GetFileSizeInBytes(string imageUrl)
|
||||
{
|
||||
string filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", imageUrl.TrimStart('/').Replace("/","\\"));
|
||||
FileInfo fileInfo = new FileInfo(filePath);
|
||||
return fileInfo.Exists ? fileInfo.Length : 0;
|
||||
}
|
||||
|
||||
// Helper method to convert the original image URL to a 400px JPEG URL in the "jpg" folder
|
||||
private string ConvertToJpgUrl(string originalFileName)
|
||||
{
|
||||
// Get the filename without the extension
|
||||
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(originalFileName);
|
||||
|
||||
// Construct the full URL for the 400px JPEG in the "jpg" folder
|
||||
string jpgUrl = $"images/jpg/{fileNameWithoutExtension}-400.jpg";
|
||||
|
||||
return jpgUrl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
//await SendEmail.Execute();
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("samantha-lynwood")]
|
||||
public IActionResult SamanthaLynwood()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
public IActionResult ThankYou()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private bool IsMobile(HttpRequest request)
|
||||
{
|
||||
string userAgent = request.Headers["User-Agent"].ToString().ToLower();
|
||||
return userAgent.Contains("mobi") ||
|
||||
userAgent.Contains("android") ||
|
||||
userAgent.Contains("iphone") ||
|
||||
userAgent.Contains("ipad");
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
13
CatherineLynwood/Controllers/PreviewController.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CatherineLynwood.Controllers
|
||||
{
|
||||
[Route("preview-group")]
|
||||
public class PreviewController : Controller
|
||||
{
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
||||
110
CatherineLynwood/Controllers/SitemapController.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
using CatherineLynwood.Models;
|
||||
using CatherineLynwood.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CatherineLynwood.Controllers
|
||||
{
|
||||
public class SitemapController : Controller
|
||||
{
|
||||
private DataAccess _dataAccess;
|
||||
|
||||
public SitemapController(DataAccess dataAccess)
|
||||
{
|
||||
_dataAccess = dataAccess;
|
||||
}
|
||||
|
||||
// Action to generate the sitemap dynamically
|
||||
[HttpGet("sitemap.xml")]
|
||||
public async Task<IActionResult> Sitemap()
|
||||
{
|
||||
var urls = await GetUrlsForSitemap();
|
||||
var sitemap = GenerateSitemapXml(urls);
|
||||
|
||||
return Content(sitemap, "application/xml", Encoding.UTF8);
|
||||
}
|
||||
|
||||
// Method to retrieve all URLs for the sitemap
|
||||
private async Task<List<SitemapEntry>> GetUrlsForSitemap()
|
||||
{
|
||||
var urls = new List<SitemapEntry>
|
||||
{
|
||||
new SitemapEntry { Url = Url.Action("Index", "Home", null, Request.Scheme).TrimEnd('/'), LastModified = DateTime.UtcNow },
|
||||
new SitemapEntry { Url = Url.Action("ContactCatherine", "Home", null, Request.Scheme).TrimEnd('/'), LastModified = DateTime.UtcNow },
|
||||
new SitemapEntry { Url = Url.Action("AboutCatherineLynwood", "Home", null, Request.Scheme).TrimEnd('/'), LastModified = DateTime.UtcNow },
|
||||
new SitemapEntry { Url = Url.Action("SamanthaLynwood", "Home", null, Request.Scheme).TrimEnd('/'), LastModified = DateTime.UtcNow },
|
||||
new SitemapEntry { Url = Url.Action("Index", "AskAQuestion", null, Request.Scheme).TrimEnd('/'), LastModified = DateTime.UtcNow },
|
||||
new SitemapEntry { Url = Url.Action("Index", "TheAlphaFlame", null, Request.Scheme).TrimEnd('/'), LastModified = DateTime.UtcNow },
|
||||
new SitemapEntry { Url = Url.Action("Blog", "TheAlphaFlame", null, Request.Scheme).TrimEnd('/'), LastModified = DateTime.UtcNow },
|
||||
new SitemapEntry { Url = Url.Action("Characters", "TheAlphaFlame", null, Request.Scheme).TrimEnd('/'), LastModified = DateTime.UtcNow },
|
||||
// Additional static pages
|
||||
};
|
||||
|
||||
string[] characters = { "Maggie", "Beth", "Rosie", "Rob", "Zoe", "Rick", "Rebecca", "Sophie" };
|
||||
|
||||
foreach(var character in characters)
|
||||
{
|
||||
urls.Add(new SitemapEntry
|
||||
{
|
||||
Url = Url.Action(character, "TheAlphaFlame", null, Request.Scheme).TrimEnd('/'),
|
||||
LastModified = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
// Add blog URLs dynamically, with PublishDate as LastModified
|
||||
BlogIndex blogIndex = await _dataAccess.GetBlogsAsync(null);
|
||||
foreach (var post in blogIndex.Blogs)
|
||||
{
|
||||
urls.Add(new SitemapEntry
|
||||
{
|
||||
Url = Url.Action("BlogItem", "TheAlphaFlame", new { slug = post.BlogUrl }, Request.Scheme).TrimEnd('/'),
|
||||
LastModified = post.PublishDate
|
||||
});
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
// Generate XML sitemap from URL list
|
||||
private string GenerateSitemapXml(List<SitemapEntry> entries)
|
||||
{
|
||||
var xmlSettings = new XmlWriterSettings
|
||||
{
|
||||
Indent = true,
|
||||
Encoding = Encoding.UTF8,
|
||||
OmitXmlDeclaration = false // Ensures the XML declaration is included
|
||||
};
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (var xmlWriter = XmlWriter.Create(memoryStream, xmlSettings))
|
||||
{
|
||||
xmlWriter.WriteStartDocument(); // Writes <?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
// Write <urlset> with both sitemap and xhtml namespaces
|
||||
xmlWriter.WriteStartElement("urlset", "http://www.sitemaps.org/schemas/sitemap/0.9");
|
||||
xmlWriter.WriteAttributeString("xmlns", "xhtml", null, "http://www.w3.org/1999/xhtml");
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
xmlWriter.WriteStartElement("url");
|
||||
xmlWriter.WriteElementString("loc", entry.Url);
|
||||
xmlWriter.WriteElementString("lastmod", entry.LastModified.ToString("yyyy-MM-dd"));
|
||||
xmlWriter.WriteElementString("changefreq", "weekly");
|
||||
xmlWriter.WriteElementString("priority", "0.5");
|
||||
xmlWriter.WriteEndElement();
|
||||
}
|
||||
|
||||
xmlWriter.WriteEndElement(); // Closes <urlset>
|
||||
xmlWriter.WriteEndDocument(); // Closes document
|
||||
xmlWriter.Flush(); // Explicit flush to ensure all data is written
|
||||
}
|
||||
|
||||
// Convert the memory stream to a UTF-8 encoded string
|
||||
return Encoding.UTF8.GetString(memoryStream.ToArray());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
231
CatherineLynwood/Controllers/TheAlphaFlame.cs
Normal file
@ -0,0 +1,231 @@
|
||||
using CatherineLynwood.Helpers;
|
||||
using CatherineLynwood.Models;
|
||||
using CatherineLynwood.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
using SendGrid.Helpers.Mail;
|
||||
|
||||
namespace CatherineLynwood.Controllers
|
||||
{
|
||||
[Route("the-alpha-flame")]
|
||||
public class TheAlphaFlame : Controller
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private DataAccess _dataAccess;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
public TheAlphaFlame(DataAccess dataAccess)
|
||||
{
|
||||
_dataAccess = dataAccess;
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Public Methods
|
||||
|
||||
[Route("characters/beth-fletcher")]
|
||||
public IActionResult Beth()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("blog")]
|
||||
public async Task<IActionResult> Blog(BlogFilter blogFilter)
|
||||
{
|
||||
// Convert the Categories list to a comma-separated string for querying
|
||||
string categoryIDs = blogFilter.Categories != null ? string.Join(",", blogFilter.Categories) : string.Empty;
|
||||
|
||||
// Retrieve the blogs filtered by categories
|
||||
BlogIndex blogIndex = await _dataAccess.GetBlogsAsync(categoryIDs);
|
||||
blogIndex.BlogFilter = blogFilter;
|
||||
blogIndex.BlogFilter.TotalPages = (int)Math.Ceiling((double)blogIndex.Blogs.Count / blogFilter.ResultsPerPage);
|
||||
blogIndex.IsMobile = IsMobile(Request);
|
||||
|
||||
if (blogFilter.TotalPages != blogFilter.PreviousTotalPages)
|
||||
{
|
||||
blogFilter.PageNumber = 1;
|
||||
}
|
||||
|
||||
// Determine sorting direction: 1 = newest first, 2 = oldest first
|
||||
if (blogFilter.SortDirection == 1)
|
||||
{
|
||||
// Sort by newest first
|
||||
blogIndex.Blogs = blogIndex.Blogs.OrderByDescending(b => b.PublishDate).ToList();
|
||||
}
|
||||
else if (blogFilter.SortDirection == 2)
|
||||
{
|
||||
// Sort by oldest first
|
||||
blogIndex.Blogs = blogIndex.Blogs.OrderBy(b => b.PublishDate).ToList();
|
||||
}
|
||||
|
||||
// Paginate the results based on ResultsPerPage
|
||||
int resultsPerPage = blogFilter.ResultsPerPage > 0 ? blogFilter.ResultsPerPage : 10; // Default to 10 if not specified
|
||||
|
||||
// Calculate the items for the current page
|
||||
blogIndex.Blogs = blogIndex.Blogs
|
||||
.Skip((blogFilter.PageNumber - 1) * resultsPerPage)
|
||||
.Take(resultsPerPage)
|
||||
.ToList();
|
||||
|
||||
// Show advanced options if any category filters are applied
|
||||
blogIndex.ShowAdvanced = !string.IsNullOrEmpty(categoryIDs);
|
||||
|
||||
return View(blogIndex);
|
||||
}
|
||||
|
||||
[Route("blog/{slug}")]
|
||||
public async Task<IActionResult> BlogItem(string slug, bool showThanks)
|
||||
{
|
||||
Blog blog = await _dataAccess.GetBlogItemAsync(slug);
|
||||
blog.ShowThanks = showThanks;
|
||||
|
||||
if (blog.Template == "slideshow")
|
||||
{
|
||||
return View("SlideShowTemplate", blog);
|
||||
}
|
||||
|
||||
return View("DefaultTemplate", blog);
|
||||
}
|
||||
|
||||
[Route("characters")]
|
||||
public IActionResult Characters()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("comment")]
|
||||
public async Task<IActionResult> Comment(BlogComment blogComment, string blogUrl)
|
||||
{
|
||||
bool showThanks = false;
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
bool visible = await _dataAccess.AddBlogCommentAsync(blogComment);
|
||||
|
||||
if (!visible)
|
||||
{
|
||||
var to = new EmailAddress(blogComment.EmailAddress, blogComment.Name);
|
||||
var subject = "Thank you from Catherine Lynwood Web Site";
|
||||
var plainTextContent = $"Dear {blogComment.Name},/r/nThank you for taking the time to comment on my blog post. ";
|
||||
var htmlContent = $"Dear {blogComment.Name},<br>" +
|
||||
$"<p>Thank you for taking the time to comment on my recent blog post.</p>" +
|
||||
$"<p>This email is to let you know that all comments are reviewed before appearing on the blog page on which they were made. " +
|
||||
$"This is unfortuantely necessary to avoid spam and possible offensive remarks. " +
|
||||
$"I will endevour to review your comment as soon as possible. " +
|
||||
$"Please check back soon.</p>" +
|
||||
$"<p>Catherine Lynwood<br>" +
|
||||
$"Author: The Alpha Flame<br>" +
|
||||
@$"Web: <a href=""https://www.catherinelynwood.com"">www.catherinelynwood.com</a></p>";
|
||||
await SendEmail.Execute(to, subject, plainTextContent, htmlContent);
|
||||
}
|
||||
|
||||
showThanks = true;
|
||||
}
|
||||
|
||||
PreFillContact blogPreFill = new PreFillContact
|
||||
{
|
||||
Name = blogComment.Name,
|
||||
EmailAddress = blogComment.EmailAddress,
|
||||
Age = blogComment.Age,
|
||||
Sex = blogComment.Sex
|
||||
};
|
||||
|
||||
string json = JsonConvert.SerializeObject(blogPreFill);
|
||||
|
||||
CookieOptions cookieOptions = new CookieOptions
|
||||
{
|
||||
Expires = DateTime.Now.AddYears(1)
|
||||
};
|
||||
|
||||
Response.Cookies.Append("BlogPreFill", json, cookieOptions);
|
||||
|
||||
return RedirectToAction("BlogItem", new { slug = blogUrl, showThanks = showThanks });
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("characters/maggie-grant")]
|
||||
public IActionResult Maggie()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("characters/rebecca-jones")]
|
||||
public IActionResult Rebecca()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("characters/rick")]
|
||||
public IActionResult Rick()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("characters/rob-jackson")]
|
||||
public IActionResult Rob()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("characters/rosie-macdonald")]
|
||||
public IActionResult Rosie()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("characters/sophie-jones")]
|
||||
public IActionResult Sophie()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("characters/zoe-davies")]
|
||||
public IActionResult Zoe()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("chapters/chapter-1-beth")]
|
||||
public IActionResult Chapter1()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[Route("chapters/chapter-2-maggie")]
|
||||
public IActionResult Chapter2()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
[Route("front-cover")]
|
||||
public IActionResult FrontCover()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private bool IsMobile(HttpRequest request)
|
||||
{
|
||||
string userAgent = request.Headers["User-Agent"].ToString().ToLower();
|
||||
return userAgent.Contains("mobi") ||
|
||||
userAgent.Contains("android") ||
|
||||
userAgent.Contains("iphone") ||
|
||||
userAgent.Contains("ipad");
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
17
CatherineLynwood/Helpers/SendEmail.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using SendGrid.Helpers.Mail;
|
||||
using SendGrid;
|
||||
|
||||
namespace CatherineLynwood.Helpers
|
||||
{
|
||||
public class SendEmail
|
||||
{
|
||||
public static async Task Execute(EmailAddress to, string subject, string plainTextContent, string htmlContent)
|
||||
{
|
||||
var apiKey = "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";
|
||||
var client = new SendGridClient(apiKey);
|
||||
var from = new EmailAddress("catherine@catherinelynwood.com", "Catherine Lynwood");
|
||||
var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);
|
||||
var response = await client.SendEmailAsync(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
76
CatherineLynwood/Middleware/BlockPhpRequestsMiddleware.cs
Normal file
@ -0,0 +1,76 @@
|
||||
namespace CatherineLynwood.Middleware
|
||||
{
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Web.Administration;
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
public class BlockPhpRequestsMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<BlockPhpRequestsMiddleware> _logger;
|
||||
private IWebHostEnvironment _environment;
|
||||
|
||||
public BlockPhpRequestsMiddleware(RequestDelegate next, ILogger<BlockPhpRequestsMiddleware> logger, IWebHostEnvironment environment)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
_environment = environment;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
var requestPath = context.Request.Path.Value;
|
||||
|
||||
if (requestPath != null && (requestPath.EndsWith(".php") || requestPath.EndsWith(".env")))
|
||||
{
|
||||
var ipAddress = context.Connection.RemoteIpAddress?.ToString();
|
||||
if (ipAddress != null)
|
||||
{
|
||||
_logger.LogWarning($"Detected PHP request from IP {ipAddress}.");
|
||||
|
||||
if (!_environment.IsDevelopment())
|
||||
{
|
||||
// Only attempt to block IP if not in development
|
||||
BlockIpAddressInIIS(ipAddress);
|
||||
}
|
||||
|
||||
context.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
}
|
||||
|
||||
|
||||
private void BlockIpAddressInIIS(string ipAddress)
|
||||
{
|
||||
using (var serverManager = new ServerManager())
|
||||
{
|
||||
// Replace "Default Web Site" with your actual site name
|
||||
var site = serverManager.Sites["CatherineLynwood"];
|
||||
var config = site.GetWebConfiguration();
|
||||
var ipSecuritySection = config.GetSection("system.webServer/security/ipSecurity");
|
||||
|
||||
var ipSecurityCollection = ipSecuritySection.GetCollection();
|
||||
|
||||
// Check if IP already exists in the list to avoid duplicates
|
||||
var existingEntry = ipSecurityCollection.FirstOrDefault(e => e.Attributes["ipAddress"]?.Value?.ToString() == ipAddress);
|
||||
if (existingEntry == null)
|
||||
{
|
||||
// Add a new IP restriction entry with deny access
|
||||
var addElement = ipSecurityCollection.CreateElement("add");
|
||||
addElement.SetAttributeValue("ipAddress", ipAddress);
|
||||
addElement.SetAttributeValue("allowed", false);
|
||||
ipSecurityCollection.Add(addElement);
|
||||
|
||||
serverManager.CommitChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
26
CatherineLynwood/Middleware/RedirectToWwwMiddleware.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace CatherineLynwood.Middleware
|
||||
{
|
||||
public class RedirectToWwwMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public RedirectToWwwMiddleware(RequestDelegate next)
|
||||
{
|
||||
_next = next;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
var host = context.Request.Host.Host;
|
||||
if (host.Equals("catherinelynwood.com", System.StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var newUrl = $"https://www.catherinelynwood.com{context.Request.Path}{context.Request.QueryString}";
|
||||
context.Response.Redirect(newUrl, permanent: true);
|
||||
return; // End the middleware pipeline.
|
||||
}
|
||||
|
||||
// Continue to the next middleware.
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
62
CatherineLynwood/Models/Blog.cs
Normal file
@ -0,0 +1,62 @@
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class Blog
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public string AudioTeaserText { get; set; }
|
||||
|
||||
public string AudioTeaserUrl { get; set; }
|
||||
|
||||
public string AudioTranscriptUrl { get; set; }
|
||||
|
||||
public BlogComment BlogComment { get; set; } = new BlogComment();
|
||||
|
||||
public int BlogID { get; set; }
|
||||
|
||||
public List<BlogImage> BlogImages { get; set; } = new List<BlogImage>();
|
||||
|
||||
public string BlogUrl { get; set; }
|
||||
|
||||
public string ContentBottom { get; set; }
|
||||
|
||||
public string ContentTop { get; set; }
|
||||
|
||||
public string ImageAlt { get; set; }
|
||||
|
||||
public string ImageDescription { get; set; }
|
||||
|
||||
public bool ImageFirst { get; set; }
|
||||
|
||||
public string ImageUrl { get; set; }
|
||||
|
||||
public string IndexText { get; set; }
|
||||
|
||||
public int Likes { get; set; }
|
||||
|
||||
public DateTime PublishDate { get; set; }
|
||||
|
||||
public bool ShowThanks { get; set; }
|
||||
|
||||
public string SubTitle { get; set; }
|
||||
|
||||
public string Template { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public string DefaultWebpImage
|
||||
{
|
||||
get
|
||||
{
|
||||
string fileName = Path.GetFileNameWithoutExtension(ImageUrl);
|
||||
|
||||
|
||||
return $"{fileName}-600.webp";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
15
CatherineLynwood/Models/BlogCategory.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class BlogCategory
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public string Category { get; set; }
|
||||
|
||||
public int CategoryID { get; set; }
|
||||
|
||||
public bool Selected { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
31
CatherineLynwood/Models/BlogComment.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class BlogComment
|
||||
{
|
||||
[Required(ErrorMessage = "Please enter your age.")]
|
||||
[Range(18, 120, ErrorMessage = "You must be at least 18 years old.")]
|
||||
[Display(Name = "Your Age", Prompt = "Your age")]
|
||||
public int Age { get; set; }
|
||||
|
||||
public int BlogID { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please enter your message.")]
|
||||
[Display(Name = "Your Message", Prompt = "Your message")]
|
||||
public string Comment { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please enter your email address.")]
|
||||
[EmailAddress(ErrorMessage = "Please enter a valid email address.")]
|
||||
[Display(Name = "Your Email Address", Prompt = "Your email address")]
|
||||
public string EmailAddress { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please enter your name.")]
|
||||
[Display(Name = "Your Name", Prompt = "Your name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please enter your sex")]
|
||||
[Display(Name = "Your Sex", Prompt = "Your sex")]
|
||||
public string Sex { get; set; }
|
||||
}
|
||||
}
|
||||
19
CatherineLynwood/Models/BlogCommentExisting.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class BlogCommentExisting : BlogComment
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public DateTime CommentDate { get; set; } = new DateTime();
|
||||
|
||||
public string ImageUrl { get; set; }
|
||||
|
||||
public string Reply { get; set; }
|
||||
|
||||
public string ResponderName { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
15
CatherineLynwood/Models/BlogComments.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class BlogComments : BlogCommentExisting
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public int BlogID { get; set; }
|
||||
|
||||
public string BlogUrl { get; set; }
|
||||
|
||||
public List<BlogCommentExisting> ExistingComments { get; set; } = new List<BlogCommentExisting>();
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
17
CatherineLynwood/Models/BlogFilter.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class BlogFilter
|
||||
{
|
||||
public int SortDirection { get; set; } = 1;
|
||||
|
||||
public int ResultsPerPage { get; set; } = 10;
|
||||
|
||||
public int PageNumber { get; set; } = 1;
|
||||
|
||||
public List<int> Categories { get; set; }
|
||||
|
||||
public int TotalPages { get; set; }
|
||||
|
||||
public int PreviousTotalPages { get; set; }
|
||||
}
|
||||
}
|
||||
15
CatherineLynwood/Models/BlogImage.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class BlogImage
|
||||
{
|
||||
public int ImageID { get; set; }
|
||||
|
||||
public int BlogID { get; set; }
|
||||
|
||||
public string ImageUrl { get; set; }
|
||||
|
||||
public string ImageCaption { get; set; }
|
||||
|
||||
public string ImageText { get; set; }
|
||||
}
|
||||
}
|
||||
19
CatherineLynwood/Models/BlogIndex.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class BlogIndex
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public List<BlogCategory> BlogCategories { get; set; } = new List<BlogCategory>();
|
||||
|
||||
public BlogFilter BlogFilter { get; set; }
|
||||
|
||||
public List<Blog> Blogs { get; set; } = new List<Blog>();
|
||||
|
||||
public bool IsMobile { get; set; }
|
||||
|
||||
public bool ShowAdvanced { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
20
CatherineLynwood/Models/Contact.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class Contact
|
||||
{
|
||||
[Required(ErrorMessage = "Please enter your name.")]
|
||||
[Display(Name = "Your Name", Prompt = "Your name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please enter your email address.")]
|
||||
[EmailAddress(ErrorMessage = "Please enter a valid email address.")]
|
||||
[Display(Name = "Your Email Address", Prompt = "Your email address")]
|
||||
public string EmailAddress { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please enter your message.")]
|
||||
[Display(Name = "Your Message", Prompt = "Your message")]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
9
CatherineLynwood/Models/ErrorViewModel.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class ErrorViewModel
|
||||
{
|
||||
public string? RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
}
|
||||
}
|
||||
11
CatherineLynwood/Models/PageModelBase.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class PageModelBase
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public bool IsMobile { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
19
CatherineLynwood/Models/PreFillContact.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class PreFillContact
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public int Age { get; set; }
|
||||
|
||||
public string EmailAddress { get; set; } = string.Empty;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Sex { get; set; } = string.Empty;
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
31
CatherineLynwood/Models/Question.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class Question
|
||||
{
|
||||
[Required(ErrorMessage = "Please enter your age.")]
|
||||
[Range(18, 120, ErrorMessage = "You must be at least 18 years old.")]
|
||||
[Display(Name = "Your Age", Prompt = "Your age")]
|
||||
public int Age { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please enter your question.")]
|
||||
[Display(Name = "Your Question", Prompt = "Your question")]
|
||||
public string Text { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please enter your email address.")]
|
||||
[EmailAddress(ErrorMessage = "Please enter a valid email address.")]
|
||||
[Display(Name = "Your Email Address", Prompt = "Your email address")]
|
||||
public string EmailAddress { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please enter your name.")]
|
||||
[Display(Name = "Your Name", Prompt = "Your name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "Please enter your sex")]
|
||||
[Display(Name = "Your Sex", Prompt = "Your sex")]
|
||||
public string Sex { get; set; }
|
||||
|
||||
public DateTime QuestionDate { get; set; }
|
||||
}
|
||||
}
|
||||
9
CatherineLynwood/Models/Questions.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class Questions : Question
|
||||
{
|
||||
public List<Question> AskedQuestions { get; set; } = new List<Question>();
|
||||
|
||||
public bool ShowThanks { get; set; }
|
||||
}
|
||||
}
|
||||
13
CatherineLynwood/Models/SitemapEntry.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace CatherineLynwood.Models
|
||||
{
|
||||
public class SitemapEntry
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
public DateTime LastModified { get; set; }
|
||||
|
||||
public string Url { get; set; }
|
||||
|
||||
#endregion Public Properties
|
||||
}
|
||||
}
|
||||
79
CatherineLynwood/Program.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System.IO.Compression;
|
||||
|
||||
using CatherineLynwood.Middleware;
|
||||
using CatherineLynwood.Services;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
|
||||
using WebMarkupMin.AspNetCore8;
|
||||
|
||||
namespace CatherineLynwood
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Retrieve the connection string from appsettings.json
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||
|
||||
builder.Services.AddSingleton(new DataAccess(connectionString));
|
||||
|
||||
builder.Services.AddControllersWithViews();
|
||||
|
||||
// Add IHttpContextAccessor for accessing HTTP context in tag helpers
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
|
||||
// Add response compression services
|
||||
builder.Services.AddResponseCompression(options =>
|
||||
{
|
||||
options.EnableForHttps = true;
|
||||
options.Providers.Add<BrotliCompressionProvider>();
|
||||
options.Providers.Add<GzipCompressionProvider>();
|
||||
});
|
||||
|
||||
builder.Services.Configure<BrotliCompressionProviderOptions>(options =>
|
||||
{
|
||||
options.Level = CompressionLevel.Fastest;
|
||||
});
|
||||
|
||||
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
|
||||
{
|
||||
options.Level = CompressionLevel.Fastest;
|
||||
});
|
||||
|
||||
// Add HTML minification
|
||||
builder.Services.AddWebMarkupMin(options =>
|
||||
{
|
||||
options.AllowMinificationInDevelopmentEnvironment = true;
|
||||
}).AddHtmlMinification();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
app.UseHsts(); // Adds the HSTS (HTTP Strict Transport Security) header
|
||||
}
|
||||
|
||||
app.UseMiddleware<BlockPhpRequestsMiddleware>(); // Add custom middleware
|
||||
app.UseMiddleware<RedirectToWwwMiddleware>(); // Add custom middleware
|
||||
|
||||
app.UseHttpsRedirection(); // Redirect HTTP requests to HTTPS
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseWebMarkupMin();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}/{id?}");
|
||||
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
30
CatherineLynwood/Properties/launchSettings.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5119"
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "http://localhost:32618",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
},
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:32618",
|
||||
"sslPort": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
406
CatherineLynwood/Services/DataAccess.cs
Normal file
@ -0,0 +1,406 @@
|
||||
using System.Data;
|
||||
|
||||
using CatherineLynwood.Models;
|
||||
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace CatherineLynwood.Services
|
||||
{
|
||||
public class DataAccess
|
||||
{
|
||||
#region Private Fields
|
||||
|
||||
private readonly string _connectionString;
|
||||
|
||||
#endregion Private Fields
|
||||
|
||||
#region Public Constructors
|
||||
|
||||
public DataAccess(string connectionString)
|
||||
{
|
||||
_connectionString = connectionString;
|
||||
}
|
||||
|
||||
public async Task<Questions> GetQuestionsAsync()
|
||||
{
|
||||
Questions questions = new Questions();
|
||||
|
||||
using (SqlConnection conn = new SqlConnection(_connectionString))
|
||||
{
|
||||
using (SqlCommand cmd = new SqlCommand())
|
||||
{
|
||||
try
|
||||
{
|
||||
await conn.OpenAsync();
|
||||
cmd.Connection = conn;
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
cmd.CommandText = "GetQuestions";
|
||||
|
||||
using (SqlDataReader rdr = await cmd.ExecuteReaderAsync())
|
||||
{
|
||||
while (await rdr.ReadAsync())
|
||||
{
|
||||
questions.AskedQuestions.Add(new Question
|
||||
{
|
||||
Age = GetDataInt(rdr, "Age"),
|
||||
EmailAddress = GetDataString(rdr, "EmailAddress"),
|
||||
Name = GetDataString(rdr, "Name"),
|
||||
Text = GetDataString(rdr, "Question"),
|
||||
QuestionDate = GetDataDate(rdr, "QuestionDate"),
|
||||
Sex = GetDataString(rdr, "Sex")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return questions;
|
||||
}
|
||||
|
||||
public async Task<BlogIndex> GetBlogsAsync(string categoryIDs)
|
||||
{
|
||||
BlogIndex blogIndex = new BlogIndex();
|
||||
|
||||
using (SqlConnection conn = new SqlConnection(_connectionString))
|
||||
{
|
||||
using (SqlCommand cmd = new SqlCommand())
|
||||
{
|
||||
try
|
||||
{
|
||||
await conn.OpenAsync();
|
||||
cmd.Connection = conn;
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
cmd.CommandText = "GetBlog";
|
||||
cmd.Parameters.AddWithValue("@CategoryIDs", categoryIDs);
|
||||
|
||||
using (SqlDataReader rdr = await cmd.ExecuteReaderAsync())
|
||||
{
|
||||
while (await rdr.ReadAsync())
|
||||
{
|
||||
blogIndex.BlogCategories.Add(new BlogCategory
|
||||
{
|
||||
CategoryID = GetDataInt(rdr, "CategoryID"),
|
||||
Category = GetDataString(rdr, "Category"),
|
||||
Selected = GetDataBool(rdr, "Selected")
|
||||
});
|
||||
}
|
||||
|
||||
await rdr.NextResultAsync();
|
||||
|
||||
while (await rdr.ReadAsync())
|
||||
{
|
||||
blogIndex.Blogs.Add(new Blog
|
||||
{
|
||||
BlogID = GetDataInt(rdr, "BlogID"),
|
||||
BlogUrl = GetDataString(rdr, "BlogUrl"),
|
||||
Title = GetDataString(rdr, "Title"),
|
||||
SubTitle = GetDataString(rdr, "SubTitle"),
|
||||
PublishDate = GetDataDate(rdr, "PublishDate"),
|
||||
Likes = GetDataInt(rdr, "Likes"),
|
||||
IndexText = GetDataString(rdr, "IndexText"),
|
||||
ImageUrl = GetDataString(rdr, "ImageUrl"),
|
||||
ImageAlt = GetDataString(rdr, "ImageAlt"),
|
||||
ImageDescription = GetDataString(rdr, "ImageDescription")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blogIndex;
|
||||
}
|
||||
|
||||
public async Task<Blog> GetBlogItemAsync(string blogUrl)
|
||||
{
|
||||
Blog blog = new Blog();
|
||||
|
||||
using (SqlConnection conn = new SqlConnection(_connectionString))
|
||||
{
|
||||
using (SqlCommand cmd = new SqlCommand())
|
||||
{
|
||||
try
|
||||
{
|
||||
await conn.OpenAsync();
|
||||
cmd.Connection = conn;
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
cmd.CommandText = "GetBlogItem";
|
||||
cmd.Parameters.AddWithValue("@BlogUrl", blogUrl);
|
||||
|
||||
using (SqlDataReader rdr = await cmd.ExecuteReaderAsync())
|
||||
{
|
||||
while (await rdr.ReadAsync())
|
||||
{
|
||||
blog.AudioTeaserText = GetDataString(rdr, "AudioTeaserText");
|
||||
blog.AudioTeaserUrl = GetDataString(rdr, "AudioTeaserUrl");
|
||||
blog.AudioTranscriptUrl = GetDataString(rdr, "AudioTranscriptUrl");
|
||||
blog.BlogID = GetDataInt(rdr, "BlogID");
|
||||
blog.BlogUrl = GetDataString(rdr, "BlogUrl");
|
||||
blog.ContentBottom = GetDataString(rdr, "ContentBottom");
|
||||
blog.ContentTop = GetDataString(rdr, "ContentTop");
|
||||
blog.ImageAlt = GetDataString(rdr, "ImageAlt");
|
||||
blog.ImageDescription = GetDataString(rdr, "ImageDescription");
|
||||
blog.ImageFirst = GetDataBool(rdr, "ImageFirst");
|
||||
blog.ImageUrl = GetDataString(rdr, "ImageUrl");
|
||||
blog.IndexText = GetDataString(rdr, "IndexText");
|
||||
blog.Likes = GetDataInt(rdr, "Likes");
|
||||
blog.PublishDate = GetDataDate(rdr, "PublishDate");
|
||||
blog.SubTitle = GetDataString(rdr, "SubTitle");
|
||||
blog.Template = GetDataString(rdr, "Template");
|
||||
blog.Title = GetDataString(rdr, "Title");
|
||||
}
|
||||
|
||||
await rdr.NextResultAsync();
|
||||
|
||||
while (await rdr.ReadAsync())
|
||||
{
|
||||
blog.BlogImages.Add(new BlogImage
|
||||
{
|
||||
ImageID = GetDataInt(rdr, "ImageID"),
|
||||
BlogID = GetDataInt(rdr, "BlogID"),
|
||||
ImageUrl = GetDataString(rdr, "ImageUrl"),
|
||||
ImageCaption = GetDataString(rdr, "ImageCaption"),
|
||||
ImageText = GetDataString(rdr, "ImageText")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blog;
|
||||
}
|
||||
|
||||
public async Task<BlogComments> GetBlogCommentsAsync(int blogID)
|
||||
{
|
||||
BlogComments blogComments = new BlogComments();
|
||||
|
||||
using (SqlConnection conn = new SqlConnection(_connectionString))
|
||||
{
|
||||
using (SqlCommand cmd = new SqlCommand())
|
||||
{
|
||||
try
|
||||
{
|
||||
await conn.OpenAsync();
|
||||
cmd.Connection = conn;
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
cmd.CommandText = "GetBlogComments";
|
||||
cmd.Parameters.AddWithValue("@BlogID", blogID);
|
||||
|
||||
using (SqlDataReader rdr = await cmd.ExecuteReaderAsync())
|
||||
{
|
||||
while (await rdr.ReadAsync())
|
||||
{
|
||||
blogComments.BlogID = GetDataInt(rdr, "BlogID");
|
||||
blogComments.BlogUrl = GetDataString(rdr, "BlogUrl");
|
||||
}
|
||||
|
||||
await rdr.NextResultAsync();
|
||||
|
||||
while (await rdr.ReadAsync())
|
||||
{
|
||||
blogComments.ExistingComments.Add(new BlogCommentExisting
|
||||
{
|
||||
BlogID = GetDataInt(rdr, "BlogID"),
|
||||
Comment = GetDataString(rdr, "Comment"),
|
||||
CommentDate = GetDataDate(rdr, "CommentDate"),
|
||||
EmailAddress = GetDataString(rdr, "EmailAddress"),
|
||||
Name = GetDataString(rdr, "Name"),
|
||||
Reply = GetDataString(rdr, "Reply"),
|
||||
ResponderName = GetDataString(rdr, "ResponderName"),
|
||||
ImageUrl = GetDataString(rdr, "ImageUrl")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return blogComments;
|
||||
}
|
||||
|
||||
public async Task<bool> AddBlogCommentAsync(BlogComment blogComment)
|
||||
{
|
||||
bool visible = false;
|
||||
|
||||
using (SqlConnection conn = new SqlConnection(_connectionString))
|
||||
{
|
||||
using (SqlCommand cmd = new SqlCommand())
|
||||
{
|
||||
try
|
||||
{
|
||||
await conn.OpenAsync();
|
||||
cmd.Connection = conn;
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
cmd.CommandText = "SaveBlogComment";
|
||||
cmd.Parameters.AddWithValue("@BlogID", blogComment.BlogID);
|
||||
cmd.Parameters.AddWithValue("@Comment", blogComment.Comment);
|
||||
cmd.Parameters.AddWithValue("@EmailAddress", blogComment.EmailAddress);
|
||||
cmd.Parameters.AddWithValue("@Name", blogComment.Name);
|
||||
cmd.Parameters.AddWithValue("@Sex", blogComment.Sex);
|
||||
cmd.Parameters.AddWithValue("@Age", blogComment.Age);
|
||||
|
||||
using (SqlDataReader rdr = await cmd.ExecuteReaderAsync())
|
||||
{
|
||||
while (await rdr.ReadAsync())
|
||||
{
|
||||
visible = GetDataBool(rdr, "Visible");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return visible;
|
||||
}
|
||||
|
||||
public async Task<bool> AddQuestionAsync(Question question)
|
||||
{
|
||||
bool visible = false;
|
||||
|
||||
using (SqlConnection conn = new SqlConnection(_connectionString))
|
||||
{
|
||||
using (SqlCommand cmd = new SqlCommand())
|
||||
{
|
||||
try
|
||||
{
|
||||
await conn.OpenAsync();
|
||||
cmd.Connection = conn;
|
||||
cmd.CommandType = CommandType.StoredProcedure;
|
||||
cmd.CommandText = "SaveQuestion";
|
||||
cmd.Parameters.AddWithValue("@Question", question.Text);
|
||||
cmd.Parameters.AddWithValue("@EmailAddress", question.EmailAddress);
|
||||
cmd.Parameters.AddWithValue("@Name", question.Name);
|
||||
cmd.Parameters.AddWithValue("@Sex", question.Sex);
|
||||
cmd.Parameters.AddWithValue("@Age", question.Age);
|
||||
|
||||
using (SqlDataReader rdr = await cmd.ExecuteReaderAsync())
|
||||
{
|
||||
while (await rdr.ReadAsync())
|
||||
{
|
||||
visible = GetDataBool(rdr, "Visible");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return visible;
|
||||
}
|
||||
|
||||
#endregion Public Constructors
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected bool GetDataBool(SqlDataReader rdr, string field)
|
||||
{
|
||||
int colIndex = rdr.GetOrdinal(field);
|
||||
|
||||
return rdr.IsDBNull(colIndex) ? false : rdr.GetBoolean(colIndex);
|
||||
}
|
||||
|
||||
protected byte[] GetDataBytes(SqlDataReader rdr, string field)
|
||||
{
|
||||
int colindex = rdr.GetOrdinal(field);
|
||||
|
||||
if (rdr.IsDBNull(colindex))
|
||||
{
|
||||
MemoryStream rs = new MemoryStream();
|
||||
return rs.ToArray();
|
||||
}
|
||||
|
||||
int bufferSize = 100; // Size of the BLOB buffer.
|
||||
byte[] outbyte = new byte[bufferSize];
|
||||
long startIndex = 0;
|
||||
|
||||
MemoryStream ms = new MemoryStream();
|
||||
BinaryWriter bw = new BinaryWriter(ms);
|
||||
long retval = rdr.GetBytes(colindex, startIndex, outbyte, 0, bufferSize);
|
||||
|
||||
while (retval == bufferSize)
|
||||
{
|
||||
bw.Write(outbyte);
|
||||
bw.Flush();
|
||||
|
||||
// Reposition the start index to the end of the last buffer and fill the buffer.
|
||||
startIndex += bufferSize;
|
||||
retval = rdr.GetBytes(colindex, startIndex, outbyte, 0, bufferSize);
|
||||
}
|
||||
|
||||
bw.Write(outbyte, 0, (int)retval);
|
||||
bw.Flush();
|
||||
bw.Close();
|
||||
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
protected DateTime GetDataDate(SqlDataReader rdr, string field)
|
||||
{
|
||||
int colIndex = rdr.GetOrdinal(field);
|
||||
|
||||
return rdr.IsDBNull(colIndex) ? new DateTime() : rdr.GetDateTime(colIndex);
|
||||
}
|
||||
|
||||
protected Decimal GetDataDecimal(SqlDataReader rdr, string field)
|
||||
{
|
||||
int colIndex = rdr.GetOrdinal(field);
|
||||
|
||||
return rdr.IsDBNull(colIndex) ? 0 : rdr.GetDecimal(colIndex);
|
||||
}
|
||||
|
||||
protected Int32 GetDataInt(SqlDataReader rdr, string field)
|
||||
{
|
||||
int colIndex = rdr.GetOrdinal(field);
|
||||
|
||||
return rdr.IsDBNull(colIndex) ? 0 : rdr.GetInt32(colIndex);
|
||||
}
|
||||
|
||||
protected long GetDataLong(SqlDataReader rdr, string field)
|
||||
{
|
||||
int colIndex = rdr.GetOrdinal(field);
|
||||
|
||||
return rdr.IsDBNull(colIndex) ? 0 : rdr.GetInt64(colIndex);
|
||||
}
|
||||
|
||||
protected string GetDataString(SqlDataReader rdr, string field)
|
||||
{
|
||||
int colIndex = rdr.GetOrdinal(field);
|
||||
|
||||
return rdr.IsDBNull(colIndex) ? string.Empty : rdr.GetString(colIndex);
|
||||
}
|
||||
|
||||
protected TimeSpan GetDataTimeSpan(SqlDataReader rdr, string field)
|
||||
{
|
||||
int colIndex = rdr.GetOrdinal(field);
|
||||
|
||||
return rdr.IsDBNull(colIndex) ? new TimeSpan() : rdr.GetTimeSpan(colIndex);
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
}
|
||||
}
|
||||
133
CatherineLynwood/TagHelpers/MetaTagHelper.cs
Normal file
@ -0,0 +1,133 @@
|
||||
using System.Text.Json;
|
||||
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
||||
namespace CatherineLynwood.TagHelpers
|
||||
{
|
||||
[HtmlTargetElement("MetaTag")]
|
||||
public class MetaTagHelper : TagHelper
|
||||
{
|
||||
// Shared properties
|
||||
public string MetaTitle { get; set; }
|
||||
public string MetaDescription { get; set; }
|
||||
public string MetaKeywords { get; set; }
|
||||
public string MetaAuthor { get; set; }
|
||||
public string MetaUrl { get; set; }
|
||||
public string MetaImage { get; set; }
|
||||
public string MetaImageAlt { get; set; }
|
||||
|
||||
// Open Graph specific properties
|
||||
public string OgSiteName { get; set; }
|
||||
public string OgType { get; set; } = "article";
|
||||
|
||||
// Article specific properties
|
||||
public DateTime? ArticlePublishedTime { get; set; }
|
||||
public DateTime? ArticleModifiedTime { get; set; }
|
||||
|
||||
// Twitter specific properties
|
||||
public string TwitterCardType { get; set; } = "summary_large_image";
|
||||
public string TwitterSiteHandle { get; set; }
|
||||
public string TwitterCreatorHandle { get; set; }
|
||||
public int? TwitterPlayerWidth { get; set; }
|
||||
public int? TwitterPlayerHeight { get; set; }
|
||||
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
output.TagName = null; // Suppress output tag
|
||||
var metaTags = new System.Text.StringBuilder();
|
||||
|
||||
// General meta tags
|
||||
if (!string.IsNullOrWhiteSpace(MetaDescription))
|
||||
metaTags.AppendLine($"<meta name=\"description\" content=\"{MetaDescription}\">");
|
||||
if (!string.IsNullOrWhiteSpace(MetaKeywords))
|
||||
metaTags.AppendLine($"<meta name=\"keywords\" content=\"{MetaKeywords}\">");
|
||||
if (!string.IsNullOrWhiteSpace(MetaAuthor))
|
||||
metaTags.AppendLine($"<meta name=\"author\" content=\"{MetaAuthor}\">");
|
||||
|
||||
// Open Graph meta tags
|
||||
if (!string.IsNullOrWhiteSpace(MetaTitle))
|
||||
metaTags.AppendLine($"<meta property=\"og:title\" content=\"{MetaTitle}\">");
|
||||
if (!string.IsNullOrWhiteSpace(MetaDescription))
|
||||
metaTags.AppendLine($"<meta property=\"og:description\" content=\"{MetaDescription}\">");
|
||||
if (!string.IsNullOrWhiteSpace(OgType))
|
||||
metaTags.AppendLine($"<meta property=\"og:type\" content=\"{OgType}\">");
|
||||
if (!string.IsNullOrWhiteSpace(MetaUrl))
|
||||
metaTags.AppendLine($"<meta property=\"og:url\" content=\"{MetaUrl}\">");
|
||||
if (!string.IsNullOrWhiteSpace(MetaImage))
|
||||
metaTags.AppendLine($"<meta property=\"og:image\" content=\"{MetaImage}\">");
|
||||
if (!string.IsNullOrWhiteSpace(MetaImageAlt))
|
||||
metaTags.AppendLine($"<meta property=\"og:image:alt\" content=\"{MetaImageAlt}\">");
|
||||
if (!string.IsNullOrWhiteSpace(OgSiteName))
|
||||
metaTags.AppendLine($"<meta property=\"og:site_name\" content=\"{OgSiteName}\">");
|
||||
if (ArticlePublishedTime.HasValue)
|
||||
metaTags.AppendLine($"<meta property=\"article:published_time\" content=\"{ArticlePublishedTime.Value:yyyy-MM-ddTHH:mm:ssZ}\">");
|
||||
if (ArticleModifiedTime.HasValue)
|
||||
metaTags.AppendLine($"<meta property=\"article:modified_time\" content=\"{ArticleModifiedTime.Value:yyyy-MM-ddTHH:mm:ssZ}\">");
|
||||
|
||||
// Twitter meta tags
|
||||
if (!string.IsNullOrWhiteSpace(TwitterCardType))
|
||||
metaTags.AppendLine($"<meta name=\"twitter:card\" content=\"{TwitterCardType}\">");
|
||||
if (!string.IsNullOrWhiteSpace(MetaTitle))
|
||||
metaTags.AppendLine($"<meta name=\"twitter:title\" content=\"{MetaTitle}\">");
|
||||
if (!string.IsNullOrWhiteSpace(MetaDescription))
|
||||
metaTags.AppendLine($"<meta name=\"twitter:description\" content=\"{MetaDescription}\">");
|
||||
if (!string.IsNullOrWhiteSpace(TwitterSiteHandle))
|
||||
metaTags.AppendLine($"<meta name=\"twitter:site\" content=\"{TwitterSiteHandle}\">");
|
||||
if (!string.IsNullOrWhiteSpace(TwitterCreatorHandle))
|
||||
metaTags.AppendLine($"<meta name=\"twitter:creator\" content=\"{TwitterCreatorHandle}\">");
|
||||
if (!string.IsNullOrWhiteSpace(MetaImage))
|
||||
metaTags.AppendLine($"<meta name=\"twitter:image\" content=\"{MetaImage}\">");
|
||||
if (!string.IsNullOrWhiteSpace(MetaImageAlt))
|
||||
metaTags.AppendLine($"<meta name=\"twitter:image:alt\" content=\"{MetaImageAlt}\">");
|
||||
if (!string.IsNullOrWhiteSpace(MetaUrl))
|
||||
{
|
||||
metaTags.AppendLine($"<meta name=\"twitter:player\" content=\"{MetaUrl}\">");
|
||||
if (TwitterPlayerWidth.HasValue)
|
||||
metaTags.AppendLine($"<meta name=\"twitter:player:width\" content=\"{TwitterPlayerWidth}\">");
|
||||
if (TwitterPlayerHeight.HasValue)
|
||||
metaTags.AppendLine($"<meta name=\"twitter:player:height\" content=\"{TwitterPlayerHeight}\">");
|
||||
}
|
||||
|
||||
// JSON-LD for schema.org
|
||||
var jsonLd = new
|
||||
{
|
||||
@context = "https://schema.org",
|
||||
@type = "WebPage",
|
||||
name = MetaTitle,
|
||||
description = MetaDescription,
|
||||
url = MetaUrl,
|
||||
author = new
|
||||
{
|
||||
@type = "Person",
|
||||
name = MetaAuthor,
|
||||
url = MetaUrl
|
||||
},
|
||||
publisher = new
|
||||
{
|
||||
@type = "Organization",
|
||||
name = MetaAuthor,
|
||||
url = MetaUrl
|
||||
},
|
||||
mainEntityOfPage = new
|
||||
{
|
||||
@type = "WebPage",
|
||||
@id = MetaUrl
|
||||
},
|
||||
inLanguage = "en",
|
||||
datePublished = ArticlePublishedTime?.ToString("yyyy-MM-dd"),
|
||||
dateModified = ArticleModifiedTime?.ToString("yyyy-MM-dd"),
|
||||
headline = MetaTitle
|
||||
};
|
||||
|
||||
string jsonLdString = JsonSerializer.Serialize(jsonLd, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
metaTags.AppendLine($"<script type=\"application/ld+json\">{jsonLdString}</script>");
|
||||
|
||||
// Output all content
|
||||
output.Content.SetHtmlContent(metaTags.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
123
CatherineLynwood/TagHelpers/ResponsiveImageTagHelper.cs
Normal file
@ -0,0 +1,123 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
||||
using SixLabors.Fonts;
|
||||
using SixLabors.ImageSharp.Drawing.Processing;
|
||||
using SixLabors.ImageSharp.Formats.Jpeg;
|
||||
using SixLabors.ImageSharp.Formats.Webp;
|
||||
|
||||
namespace CatherineLynwood.TagHelpers
|
||||
{
|
||||
[HtmlTargetElement("responsive-image")]
|
||||
public class ResponsiveImageTagHelper : TagHelper
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public ResponsiveImageTagHelper(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
[HtmlAttributeName("src")]
|
||||
public string Source { get; set; }
|
||||
|
||||
[HtmlAttributeName("alt")]
|
||||
public string AltText { get; set; }
|
||||
|
||||
[HtmlAttributeName("class")]
|
||||
public string CssClass { get; set; }
|
||||
|
||||
[HtmlAttributeName("display-width-percentage")]
|
||||
public int DisplayWidthPercentage { get; set; } = 100;
|
||||
|
||||
private readonly string[] _resolutions = { "1920", "1400", "1200", "992", "768", "576" };
|
||||
private readonly string _wwwRoot = "wwwroot";
|
||||
|
||||
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
// Adjust for mobile display width if applicable
|
||||
if (IsMobile(_httpContextAccessor.HttpContext.Request))
|
||||
{
|
||||
DisplayWidthPercentage = 100;
|
||||
}
|
||||
|
||||
string rootPath = Path.Combine(Directory.GetCurrentDirectory(), _wwwRoot, "images", Source);
|
||||
string directory = Path.GetDirectoryName(rootPath);
|
||||
string fileName = Path.GetFileNameWithoutExtension(rootPath);
|
||||
string extension = Path.GetExtension(rootPath);
|
||||
|
||||
// Define paths for "webp" and "jpg" subfolders
|
||||
string webpDirectory = Path.Combine(directory, "webp");
|
||||
string jpgDirectory = Path.Combine(directory, "jpg");
|
||||
string watermarkText = "© Catherine Lynwood 2024";
|
||||
|
||||
// Ensure the subdirectories exist
|
||||
Directory.CreateDirectory(webpDirectory);
|
||||
Directory.CreateDirectory(jpgDirectory);
|
||||
|
||||
// Generate HTML for <picture> tag with WebP sources only
|
||||
output.TagName = "picture";
|
||||
foreach (string res in _resolutions)
|
||||
{
|
||||
int effectiveWidth = int.Parse(res) * DisplayWidthPercentage / 100;
|
||||
string webpPath = Path.Combine(webpDirectory, $"{fileName}-{effectiveWidth}.webp");
|
||||
|
||||
if (!File.Exists(webpPath))
|
||||
{
|
||||
await CreateResizedImage(rootPath, webpPath, effectiveWidth, watermarkText, true); // WebP format
|
||||
}
|
||||
|
||||
output.Content.AppendHtml(
|
||||
$"<source srcset='/images/webp/{fileName}-{effectiveWidth}.webp' media='(min-width: {res}px)'>");
|
||||
}
|
||||
|
||||
string webpPathFallback = Path.Combine(webpDirectory, $"{fileName}-400.webp");
|
||||
|
||||
if (!File.Exists(webpPathFallback))
|
||||
{
|
||||
await CreateResizedImage(rootPath, webpPathFallback, 400, watermarkText, true); // WebP format
|
||||
}
|
||||
|
||||
// Generate the 400px JPEG for RSS feed or other uses (without adding to the <picture> element)
|
||||
string jpgFallbackPath = Path.Combine(jpgDirectory, $"{fileName}-400.jpg");
|
||||
if (!File.Exists(jpgFallbackPath))
|
||||
{
|
||||
await CreateResizedImage(rootPath, jpgFallbackPath, 400, watermarkText, false); // JPEG format
|
||||
}
|
||||
|
||||
// Add the default WebP fallback in the <img> tag (JPEG is not included in HTML)
|
||||
output.Content.AppendHtml($"<img src='/images/webp/{fileName}-400.webp' class='{CssClass}' alt='{AltText}'>");
|
||||
}
|
||||
|
||||
|
||||
private async Task CreateResizedImage(string originalPath, string outputPath, int width, string watermarkText, bool isWebP)
|
||||
{
|
||||
using (var image = await Image.LoadAsync(originalPath))
|
||||
{
|
||||
image.Mutate(x => x
|
||||
.Resize(width, 0)
|
||||
.DrawText(watermarkText, SystemFonts.CreateFont("Arial", 12), Color.White, new PointF(30, image.Height - 20)));
|
||||
|
||||
if (isWebP)
|
||||
{
|
||||
var webpEncoder = new WebpEncoder { Quality = 75 };
|
||||
await image.SaveAsync(outputPath, webpEncoder);
|
||||
}
|
||||
else
|
||||
{
|
||||
var jpegEncoder = new JpegEncoder { Quality = 75 };
|
||||
await image.SaveAsync(outputPath, jpegEncoder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsMobile(HttpRequest request)
|
||||
{
|
||||
string userAgent = request.Headers["User-Agent"].ToString().ToLower();
|
||||
return userAgent.Contains("mobi") ||
|
||||
userAgent.Contains("android") ||
|
||||
userAgent.Contains("iphone") ||
|
||||
userAgent.Contains("ipad");
|
||||
}
|
||||
}
|
||||
}
|
||||
49
CatherineLynwood/TagHelpers/SocialMediaShareTagHelper.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Microsoft.AspNetCore.Razor.TagHelpers;
|
||||
|
||||
namespace CatherineLynwood.TagHelpers
|
||||
{
|
||||
[HtmlTargetElement("social-media-share")]
|
||||
public class SocialMediaShareTagHelper : TagHelper
|
||||
{
|
||||
// The URL of the page to share
|
||||
public string Url { get; set; }
|
||||
|
||||
// Optional title for the shared content
|
||||
public string Title { get; set; }
|
||||
|
||||
// Additional CSS classes for the div
|
||||
public string Class { get; set; }
|
||||
|
||||
public override void Process(TagHelperContext context, TagHelperOutput output)
|
||||
{
|
||||
// Ensure Url is encoded
|
||||
var encodedUrl = $"https://www.catherinelynwood.com{Url}"; // Replace with a default if URL is not provided
|
||||
var encodedTitle = Title ?? "Check this out!";
|
||||
|
||||
// Define the social media sharing URLs
|
||||
var facebookUrl = $"https://www.facebook.com/sharer/sharer.php?u={encodedUrl}";
|
||||
var twitterUrl = $"https://twitter.com/intent/tweet?url={encodedUrl}&text={encodedTitle}";
|
||||
var linkedinUrl = $"https://www.linkedin.com/sharing/share-offsite/?url={encodedUrl}";
|
||||
var pinterestUrl = $"https://pinterest.com/pin/create/button/?url={encodedUrl}&description={encodedTitle}";
|
||||
|
||||
// Set the tag output
|
||||
output.TagName = "p"; // container for the social media buttons
|
||||
output.TagMode = TagMode.StartTagAndEndTag;
|
||||
|
||||
// Apply the passed CSS classes along with "social-media-share" default class
|
||||
var cssClass = string.IsNullOrEmpty(Class) ? "social-media-share" : $"social-media-share {Class}";
|
||||
output.Attributes.SetAttribute("class", cssClass);
|
||||
|
||||
// Construct HTML for each button
|
||||
var content = $@"
|
||||
<a href='{facebookUrl}' target='_blank' class='share-button facebook'><i class='fab fa-facebook-square'></i></a>
|
||||
<a href='{twitterUrl}' target='_blank' class='share-button twitter'><i class='fab fa-twitter-square'></i></a>
|
||||
<a href='{linkedinUrl}' target='_blank' class='share-button linkedin'><i class='fab fa-linkedin'></i></a>
|
||||
<a href='{pinterestUrl}' target='_blank' class='share-button pinterest'><i class='fab fa-pinterest-square'></i></a>
|
||||
";
|
||||
|
||||
// Set the inner HTML for the output
|
||||
output.Content.SetHtmlContent(content);
|
||||
}
|
||||
}
|
||||
}
|
||||
190
CatherineLynwood/Views/AskAQuestion/Index.cshtml
Normal file
@ -0,0 +1,190 @@
|
||||
@model CatherineLynwood.Models.Questions
|
||||
@using Humanizer
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Ask A Question";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index" class="text-light">Home</a></li>
|
||||
<li class="breadcrumb-item active text-white" aria-current="page">Ask A Question</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
@if (Model.ShowThanks)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-success alert-dismissible fade show">
|
||||
<strong class="alert-heading">Success!</strong><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button><br />
|
||||
<p>
|
||||
Your comments have been submitted and are awaiting approval. Thank you for your feedback.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Ask A Question</h1>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
The questions shown below have already been suggested, and are ones I plan to respond to in my upcoming podcast. If you have a question you'd like me
|
||||
to answer, please use the form below to ask it. Try to make sure no one else has asked it previously.
|
||||
</div>
|
||||
<div class="col-12">
|
||||
@foreach(var question in Model.AskedQuestions)
|
||||
{
|
||||
<div class="row align-items-center p-2">
|
||||
<div class="col-2 col-md-1">
|
||||
<i class="fad fa-question fa-3x text-white"></i>
|
||||
</div>
|
||||
<div class="col-10 col-md-11 bg-white text-dark rounded-3">
|
||||
<div class="row p-3">
|
||||
<div class="col-md-6">
|
||||
Asked by: @question.Name
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
Asked @question.QuestionDate.Humanize(true) on: @question.QuestionDate.ToString("dd MMMM yyyy 'at' h:mmtt")
|
||||
</div>
|
||||
</div>
|
||||
<div class="row border-top pt-2">
|
||||
<div class="col-12 pb-3">
|
||||
@question.Name asked: “<i>@question.Text</i>”
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<form asp-action="AskQuestion" class="row" id="question">
|
||||
<div class="col-12 pb-3">
|
||||
<h2>Ask A Question</h2>
|
||||
In the future I intend to record a question and answer podcast. The idea is that you will be able to ask me anything about the
|
||||
book I'm currently writing, or indeed anything else related to my work processes and ultimately getting my novel published. I might
|
||||
even answer the occasional more personal question if you're that inquisitive. Please use the form below to ask your question. As on
|
||||
our blog comments page I asked for your name, email address, age and sex, as these are all relevant to the question you're asking.
|
||||
In the event I need you to clarify your question I may email you about it before including it in the podcast so please make sure your
|
||||
email address is acurate.
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
</div>
|
||||
<div class="col-md-4 text-dark">
|
||||
<div class="form-floating mb-3">
|
||||
<input asp-for="Name" name="Name" class="form-control">
|
||||
<label asp-for="Name"></label>
|
||||
<span asp-validation-for="Name" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 text-dark">
|
||||
<div class="form-floating mb-3">
|
||||
<input asp-for="EmailAddress" name="EmailAddress" type="email" class="form-control">
|
||||
<label asp-for="EmailAddress"></label>
|
||||
<span asp-validation-for="EmailAddress" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 text-dark">
|
||||
<div class="form-floating mb-3">
|
||||
<select asp-for="Age" name="Age" class="form-select" aria-label="Select your age">
|
||||
<option value="" selected>Select your age</option>
|
||||
@for (var age = 18; age <= 120; age++)
|
||||
{
|
||||
<option value="@age">@age</option>
|
||||
})
|
||||
</select>
|
||||
<label asp-for="Age"></label>
|
||||
<span asp-validation-for="Age" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 text-dark">
|
||||
<div class="form-floating mb-3">
|
||||
<select asp-for="Sex" name="Sex" class="form-select" aria-label="Select your sex">
|
||||
<option value="" selected>Select Gender</option>
|
||||
<option value="female">Female</option>
|
||||
<option value="male">Male</option>
|
||||
<option value="other">Other</option>
|
||||
<option value="prefer-not-to-say">Prefer not to say</option>
|
||||
</select>
|
||||
<label asp-for="Sex"></label>
|
||||
<span asp-validation-for="Sex" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 text-dark">
|
||||
<div class="form-floating mb-3">
|
||||
<textarea asp-for="Text" name="Text" style="height: 40vh;" class="form-control"></textarea>
|
||||
<label asp-for="Text"></label>
|
||||
<span asp-validation-for="Text" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Terms Agreement Section -->
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<h3 class="alert-heading">Please Note</h3>
|
||||
<p>
|
||||
By asking a question, you agree to allow us to contact you regarding your question. Additionally, you consent to receive occasional updates, blog posts, and news about our latest content and book releases. You may opt out of these communications at any time.
|
||||
</p>
|
||||
<div class="form-check pb-3">
|
||||
<input type="checkbox" class="form-check-input" id="agreeTerms">
|
||||
<label class="form-check-label" for="agreeTerms">
|
||||
I agree to the terms and conditions.
|
||||
</label>
|
||||
<span id="termsWarning" class="text-danger d-none">Please accept the terms to proceed.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Placeholder Button and Submit Button -->
|
||||
<div class="d-grid">
|
||||
<button type="button" class="btn btn-secondary mb-3" id="fakeSubmitButton">Ask Question</button>
|
||||
<button type="submit" class="btn btn-dark mb-3 d-none" id="submitButton">Ask Question</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// JavaScript to handle checkbox and button toggle functionality
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const agreeCheckbox = document.getElementById("agreeTerms");
|
||||
const fakeSubmitButton = document.getElementById("fakeSubmitButton");
|
||||
const submitButton = document.getElementById("submitButton");
|
||||
const termsWarning = document.getElementById("termsWarning");
|
||||
|
||||
// Toggle buttons based on checkbox state
|
||||
agreeCheckbox.addEventListener("change", function () {
|
||||
if (this.checked) {
|
||||
fakeSubmitButton.classList.add("d-none"); // Hide placeholder button
|
||||
submitButton.classList.remove("d-none"); // Show real submit button
|
||||
termsWarning.classList.add("d-none"); // Hide warning
|
||||
} else {
|
||||
fakeSubmitButton.classList.remove("d-none"); // Show placeholder button
|
||||
submitButton.classList.add("d-none"); // Hide real submit button
|
||||
}
|
||||
});
|
||||
|
||||
// Show warning if placeholder button is clicked
|
||||
fakeSubmitButton.addEventListener("click", function () {
|
||||
termsWarning.classList.remove("d-none"); // Show warning
|
||||
agreeCheckbox.focus(); // Focus on checkbox
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Have a question for Catherine Lynwood? Ask anything about her books, characters, writing process, or themes explored in The Alpha Flame. Get involved today!">
|
||||
|
||||
<meta name="keywords" content="Catherine Lynwood, Ask a Question, The Alpha Flame, writing process, characters, novels, storytelling, author questions, book themes">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
}
|
||||
70
CatherineLynwood/Views/Home/AboutCatherineLynwood.cshtml
Normal file
@ -0,0 +1,70 @@
|
||||
@{
|
||||
ViewData["Title"] = "About Catherine Lynwood";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">About Catherine Lynwood</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="float-md-start w-md-50 p-3">
|
||||
<figure>
|
||||
<responsive-image src="catherine-lynwood-14.png" alt="Catherine Lynwood" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="50"></responsive-image>
|
||||
<figcaption>Catherin Lynwood</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
<header>
|
||||
<h1>About Catherine Lynwood</h1>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<p>Catherine Lynwood was born in Birmingham in 1980, growing up amidst the urban landscapes and industrial heritage of Britain’s second city. Raised in Rubery, Catherine was drawn to the arts from an early age, finding creative inspiration in the sights, sounds, and stories around her. She attended Colmers Farm School, where teachers quickly recognized her talent for drawing and storytelling, often encouraging her to explore her creativity in school projects and art classes.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p>Following secondary school, Catherine continued to hone her skills at Bournville College, just a stone’s throw from the famous Bournville Village. There, she immersed herself in design, illustration, and creative writing, constantly sketching ideas and characters in the margins of her notebooks. With a growing portfolio and an eye for detail, she pursued her passion further, enrolling in the art program at the University of Birmingham. Her studies in fine arts allowed her to experiment across media, from painting and illustration to digital design, while her interest in narrative and character continued to develop in parallel.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p>After university, Catherine began her career as a graphic designer, working with various agencies across Birmingham. Her work ranged from branding for local businesses to designing magazine layouts and digital content. While she found joy in design, Catherine often felt that something was missing—an unfulfilled creative ambition that kept pulling her back to the written word. She had a notebook dedicated to story ideas, character sketches, and plot outlines, slowly building a world in her imagination where her characters could live and breathe.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p>It was only after years of juggling her design work and her love for storytelling that Catherine decided to take the plunge and begin writing her first novel. Drawing on her life experiences, her love for strong, complex characters, and the rich settings of her hometown, Catherine wrote <a asp-controller="TheAlphaFlame" asp-action="Index" class="link-dark fw-bold">The Alpha Flame</a> as a deeply personal exploration of resilience, mystery, and identity. Her transition from graphic design to the literary world has allowed her to combine her eye for visual storytelling with the depth of narrative, creating a unique voice that speaks to readers on many levels.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p>Outside her creative career, Catherine’s life is deeply rooted in family. She lives just outside Birmingham with her daughter, who is her greatest inspiration and, often, her toughest critic. She describes her as having curious, quick-witted, and strong-willed—traits she values deeply and strives to cultivate in her own writing. Catherine finds joy in observing the unique perspectives her daughter brings to the world, and she often reminds her of the importance of resilience and self-discovery, themes central to her work.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p>Balancing family life with her career, Catherine believes in nurturing a creative household. She encourages her daughter <a asp-action="SamanthaLynwood" class="link-dark fw-bold">Samantha</a> to explore her passions, whether through art, music, or reading, hoping to instill in them a love for creativity and storytelling. Their family time often involves visits to museums, local theaters, or simply gathering around a table for sketching and brainstorming, making Catherine’s home a space where stories are shared and ideas take shape.</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Learn about Catherine Lynwood, author of *The Alpha Flame*, a compelling novel of romance, mystery, and family secrets. Discover Catherine’s inspirations, her journey as a novelist, and her commitment to stories featuring strong female characters and intricate plots.">
|
||||
<meta name="keywords" content="Catherine Lynwood author, about Catherine Lynwood, Catherine Lynwood biography, women’s fiction author, mystery and romance novelist, family secrets book author, female protagonists in fiction, Catherine Lynwood novels, women’s novel writer, The Alpha Flame author, female fiction writers">
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com/about-catherine-lynwood",
|
||||
"description": "Learn more about Catherine Lynwood, author of *The Alpha Flame*, a novel featuring romance, mystery, and family secrets.",
|
||||
"jobTitle": "Author",
|
||||
"knowsAbout": ["fiction writing", "mystery novels", "family drama"]
|
||||
}
|
||||
</script>
|
||||
}
|
||||
65
CatherineLynwood/Views/Home/ContactCatherine.cshtml
Normal file
@ -0,0 +1,65 @@
|
||||
@model CatherineLynwood.Models.Contact
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Contact Catherine Lynwood";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Contact Catherine Lynwood</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<form asp-action="ContactCatherine" class="row">
|
||||
<div class="col-12 col-sm-8 col-md-12">
|
||||
<h1>Contact Catherine</h1>
|
||||
<p>I would love to hear your thoughts regarding my work. Please feel free to contact me and I will try my best to reply as soon as I can.</p>
|
||||
<p>Use the form below to send Catherine a message.</p>
|
||||
</div>
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
<div class="col-12 text-dark">
|
||||
<div class="form-floating mb-3">
|
||||
<input asp-for="Name" class="form-control">
|
||||
<label asp-for="Name"></label>
|
||||
<span asp-validation-for="Name" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input asp-for="EmailAddress" type="email" class="form-control">
|
||||
<label asp-for="EmailAddress"></label>
|
||||
<span asp-validation-for="EmailAddress" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<textarea asp-for="Message" style="height: 40vh;" class="form-control"></textarea>
|
||||
<label asp-for="Message"></label>
|
||||
<span asp-validation-for="Message" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-dark btn-block mb-3">Send Message</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Get in touch with Catherine Lynwood, author of *The Alpha Flame*. For inquiries about her novels, collaborations, or just to connect, use the contact form to reach out to Catherine and stay updated on her latest work.">
|
||||
<meta name="keywords" content="contact Catherine Lynwood, connect with Catherine Lynwood, message the author, Catherine Lynwood contact, author inquiries, The Alpha Flame author, book inquiries Catherine Lynwood, women’s fiction author contact, mystery novelist contact">
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "ContactPage",
|
||||
"url": "https://www.catherinelynwood.com/contact-catherine",
|
||||
"name": "Contact Catherine Lynwood",
|
||||
"description": "Get in touch with Catherine Lynwood, author of *The Alpha Flame*, for inquiries and updates on her latest work."
|
||||
}
|
||||
</script>
|
||||
}
|
||||
78
CatherineLynwood/Views/Home/Index.cshtml
Normal file
@ -0,0 +1,78 @@
|
||||
@{
|
||||
ViewData["Title"] = "Catherine Lynwood";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item active" aria-current="page">Home</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 text-center">
|
||||
<responsive-image src="catherine-lynwood-11.png" alt="Catherine Lynwood" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="50"></responsive-image>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<header>
|
||||
<h1>Welcome to Catherine Lynwood's World</h1>
|
||||
</header>
|
||||
|
||||
<section>
|
||||
<p>Step into the pages of <em><a class="link-dark fw-bold" asp-controller="TheAlphaFlame" asp-action="Index">The Alpha Flame</a></em>, where emotions burn brightly, secrets twist through each chapter, and powerful women shape their own destinies. As a debut novelist, Catherine Lynwood invites you to experience a story crafted with depth, grit, and heart—a journey that pulls you into the lives of characters who refuse to live within anyone else’s lines.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p>In <em><a class="link-dark fw-bold" asp-controller="TheAlphaFlame" asp-action="Index">The Alpha Flame</a></em>, readers will meet Maggie Grant, a woman whose fierce intelligence and subtle dominance lead her on a thrilling path through 1980s Britain. Set against a backdrop of mystery and romance, Maggie’s story delves into the complexities of family, the weight of secrets, and the power of self-discovery. From the streets of Birmingham to quiet English countrysides, Catherine weaves a narrative that is as provocative as it is poignant.</p>
|
||||
</section>
|
||||
|
||||
<div class="text-center pb-4">
|
||||
<a asp-controller="TheAlphaFlame" asp-action="Blog" class="btn btn-dark">Read The Alpha Flame Blog</a>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<p>Catherine writes with a keen sensitivity to the struggles and triumphs of women who navigate love, loyalty, and identity on their own terms. <em><a class="link-dark fw-bold" asp-controller="TheAlphaFlame" asp-action="Index">The Alpha Flame</a></em> is not simply a romance or a thriller; it’s an exploration of strength, vulnerability, and the desire to be fully seen and heard. Each chapter unfolds with rich detail, bringing the 1980s to life in a way that feels both timeless and deeply personal.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<p>Whether you’re here to discover more about Catherine's debut or to explore the upcoming projects she’s hinted at, welcome. This is a space for stories that challenge convention and celebrate resilience. Follow along as Catherine Lynwood shares updates, reflections, and glimpses into the worlds and characters she is passionate about bringing to life.</p>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Explore the captivating world of Catherine Lynwood’s *The Alpha Flame*, a powerful novel blending romance, mystery, and family secrets set in the 1980s. Follow twin sisters as they unravel hidden truths and face emotional challenges. Discover more about Catherine Lynwood’s storytelling and insights on the official website.">
|
||||
|
||||
<meta name="keywords" content="Catherine Lynwood, The Alpha Flame novel, women's fiction, 1980s drama book, family secrets novel, twin sisters story, romance and suspense fiction, strong female characters, women’s novels, Catherine Lynwood blog, mystery novels for women, female protagonist book, family thriller">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebSite",
|
||||
"url": "https://www.catherinelynwood.com",
|
||||
"name": "Catherine Lynwood Official Website",
|
||||
"description": "The official website of Catherine Lynwood, author of *The Alpha Flame*, featuring novels with strong female characters, romance, and mystery.",
|
||||
"publisher": {
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com",
|
||||
"jobTitle": "Author",
|
||||
"knowsAbout": ["women’s fiction", "family secrets novels", "mystery"],
|
||||
"knowsLanguage": "English"
|
||||
}
|
||||
</script>
|
||||
}
|
||||
69
CatherineLynwood/Views/Home/SamanthaLynwood.cshtml
Normal file
@ -0,0 +1,69 @@
|
||||
@{
|
||||
ViewData["Title"] = "Samantha Lynwood";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="AboutCatherineLynwood">About Catherine Lynwood</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Samantha Lynwood</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="float-md-start w-md-50 p-3">
|
||||
<figure>
|
||||
<responsive-image src="samantha-lynwood-1.png" alt="Samantha Lynwood" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="50"></responsive-image>
|
||||
<figcaption>Samantha Lynwood</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<header class="character-header">
|
||||
<h1>Samantha Lynwood</h1>
|
||||
</header>
|
||||
|
||||
<section class="character-introduction">
|
||||
<p>Samantha Lynwood, 21, is a spirited media student at the University of Birmingham, where she’s immersed in the world of storytelling and communication. Studying both digital and traditional media, she has a natural talent for connecting with audiences and crafting engaging narratives. As a young woman deeply invested in both her studies and her family, Samantha lends her voice to her mother Catherine's book readings, where she embodies the character of Maggie with an impressive depth and maturity. Her unique tone and inflection bring Maggie to life, making her a fan favorite in the audiobook series.</p>
|
||||
</section>
|
||||
|
||||
<div class="row justify-content-center p-3">
|
||||
<div class="col-auto">
|
||||
<audio controls>
|
||||
<source src="/audio/samantha-lynwood.mp3" type="audio/mpeg">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="character-interests">
|
||||
<p>Beyond her academic and narration work, Samantha has a keen interest in visual media, particularly in film and photography. She spends weekends exploring Birmingham with her vintage camera, capturing scenes that resonate with her appreciation for both the gritty and the beautiful aspects of city life. A true creative, Samantha is known among her peers for her eclectic taste in music, which ranges from classic rock to indie pop. She often creates playlists for the characters she voices, which she finds helps her connect even more with their unique personalities and stories.</p>
|
||||
</section>
|
||||
|
||||
<section class="character-goals">
|
||||
<p>When she’s not studying or helping run her mother’s website, Samantha is an active member of the university’s drama club, where she occasionally acts but mostly directs short performances. She finds that directing gives her a sense of control and freedom, similar to the feeling she gets when narrating a character. Her goal after graduation is to dive into the film industry, perhaps as a director or voice artist, using her experiences and studies to carve out a distinctive path in media. Samantha's vibrant personality, combined with her knack for storytelling, makes her a natural fit for Catherine Lynwood's world, bringing depth and life to every role she takes on.</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Learn about Samantha Lynwood, audio producer of *The Alpha Flame* podcasts, a compelling novel of romance, mystery, and family secrets. Discover Catherine’s inspirations, her journey as a novelist, and her commitment to stories featuring strong female characters and intricate plots.">
|
||||
<meta name="keywords" content="Samantha Lynwood author, about Samantha Lynwood, Samantha Lynwood biography, women’s fiction author, mystery and romance novelist, family secrets book author, female protagonists in fiction, Catherine Lynwood novels, women’s novel writer, The Alpha Flame author, female fiction writers">
|
||||
<meta name="author" content="Samantha Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "Person",
|
||||
"name": "Samantha Lynwood",
|
||||
"url": "https://www.catherinelynwood.com/samantha-lynwood",
|
||||
"description": "Learn more about Samantha Lynwood, audio producer of *The Alpha Flame* podcasts, a novel featuring romance, mystery, and family secrets.",
|
||||
"jobTitle": "Audio Producer",
|
||||
"knowsAbout": ["fiction writing", "mystery novels", "family drama"]
|
||||
}
|
||||
</script>
|
||||
}
|
||||
13
CatherineLynwood/Views/Home/ThankYou.cshtml
Normal file
@ -0,0 +1,13 @@
|
||||
@{
|
||||
ViewData["Title"] = "Thank You";
|
||||
}
|
||||
|
||||
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6">
|
||||
<h1>Thank You</h1>
|
||||
<p>
|
||||
Thank you for contacting Catherine Lynwood. Your message has been received and will be reviewed as soon as possible.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
5
CatherineLynwood/Views/Preview/Index.cshtml
Normal file
@ -0,0 +1,5 @@
|
||||
@*
|
||||
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||
*@
|
||||
@{
|
||||
}
|
||||
@ -0,0 +1,160 @@
|
||||
@model CatherineLynwood.Models.BlogComments
|
||||
@using Humanizer
|
||||
|
||||
<hr />
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@if (Model.ExistingComments.Count > 0)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h2>Reader Comments</h2>
|
||||
@foreach (var comment in Model.ExistingComments)
|
||||
{
|
||||
<div class="row p-3">
|
||||
<div class="col-12 bg-white text-dark rounded-3 p-3">
|
||||
<div class="row py-2">
|
||||
<div class="col-6">
|
||||
Posted by: @comment.Name
|
||||
</div>
|
||||
<div class="col-6">
|
||||
Posted @comment.CommentDate.Humanize(true) on: @comment.CommentDate.ToString("dd MMMM yyyy 'at' h:mmtt")
|
||||
</div>
|
||||
</div>
|
||||
<div class="row border-top pt-2">
|
||||
<div class="col-12 pb-3">
|
||||
@comment.Name wrote: “<i>@comment.Comment</i>”
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(comment.Reply))
|
||||
{
|
||||
<div class="col-3 col-md-2 col-xl-1">
|
||||
<img src="~/images/webp/@comment.ImageUrl" class="img-fluid img-thumbnail rounded-circle" alt="Catherine Lynwood" />
|
||||
</div>
|
||||
<div class="col-9 col-md-10 col-xl-11 pt-2">
|
||||
@comment.ResponderName's reply: “@comment.Reply”
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
<form asp-action="Comment" class="row" id="comment">
|
||||
<div class="col-12 pb-3">
|
||||
<h2>Leave a Comment</h2>
|
||||
In invite you to leave comments regarding this blog post. As you can see I ask that you give me your name and email address, as well as your age and sex.
|
||||
This is so I can better understand my audience and tailor my content to suit your needs. I will never share your information with anyone else. I look forward to hearing from you.
|
||||
Please note that I may email you and ask for a reply before publishng your comment. This is to ensure that I am not publishing spam comments.
|
||||
</div>
|
||||
<div class="col-12 text-dark">
|
||||
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
|
||||
</div>
|
||||
<div class="col-md-4 text-dark">
|
||||
<div class="form-floating mb-3">
|
||||
<input asp-for="Name" name="Name" class="form-control">
|
||||
<label asp-for="Name"></label>
|
||||
<span asp-validation-for="Name" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 text-dark">
|
||||
<div class="form-floating mb-3">
|
||||
<input asp-for="EmailAddress" name="EmailAddress" type="email" class="form-control">
|
||||
<label asp-for="EmailAddress"></label>
|
||||
<span asp-validation-for="EmailAddress" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 text-dark">
|
||||
<div class="form-floating mb-3">
|
||||
<select asp-for="Age" name="Age" class="form-select" aria-label="Select your age">
|
||||
<option value="" selected>Select your age</option>
|
||||
@for (var age = 18; age <= 120; age++)
|
||||
{
|
||||
<option value="@age">@age</option>
|
||||
})
|
||||
</select>
|
||||
<label asp-for="Age"></label>
|
||||
<span asp-validation-for="Age" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 text-dark">
|
||||
<div class="form-floating mb-3">
|
||||
<select asp-for="Sex" name="Sex" class="form-select" aria-label="Select your sex">
|
||||
<option value="" selected>Select Gender</option>
|
||||
<option value="female">Female</option>
|
||||
<option value="male">Male</option>
|
||||
<option value="other">Other</option>
|
||||
<option value="prefer-not-to-say">Prefer not to say</option>
|
||||
</select>
|
||||
<label asp-for="Sex"></label>
|
||||
<span asp-validation-for="Sex" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 text-dark">
|
||||
<div class="form-floating mb-3">
|
||||
<textarea asp-for="Comment" name="Comment" style="height: 40vh;" class="form-control"></textarea>
|
||||
<label asp-for="Comment"></label>
|
||||
<span asp-validation-for="Comment" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Terms Agreement Section -->
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<h3 class="alert-heading">Please Note</h3>
|
||||
<p>
|
||||
By posting a comment, you agree to allow us to contact you regarding your comment. Additionally, you consent to receive occasional updates, blog posts, and news about our latest content and book releases. You may opt out of these communications at any time.
|
||||
</p>
|
||||
<div class="form-check pb-3">
|
||||
<input type="checkbox" class="form-check-input" id="agreeTerms">
|
||||
<label class="form-check-label" for="agreeTerms">
|
||||
I agree to the terms and conditions.
|
||||
</label>
|
||||
<span id="termsWarning" class="text-danger d-none">Please accept the terms to proceed.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Placeholder Button and Submit Button -->
|
||||
<div class="d-grid">
|
||||
<button type="button" class="btn btn-secondary mb-3" id="fakeSubmitButton">Post Comment</button>
|
||||
<button type="submit" class="btn btn-dark mb-3 d-none" id="submitButton">Post Comment</button>
|
||||
</div>
|
||||
|
||||
<!-- Hidden Fields -->
|
||||
<input asp-for="BlogUrl" name="BlogUrl" type="hidden" />
|
||||
<input asp-for="BlogID" name="BlogID" type="hidden" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// JavaScript to handle checkbox and button toggle functionality
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const agreeCheckbox = document.getElementById("agreeTerms");
|
||||
const fakeSubmitButton = document.getElementById("fakeSubmitButton");
|
||||
const submitButton = document.getElementById("submitButton");
|
||||
const termsWarning = document.getElementById("termsWarning");
|
||||
|
||||
// Toggle buttons based on checkbox state
|
||||
agreeCheckbox.addEventListener("change", function () {
|
||||
if (this.checked) {
|
||||
fakeSubmitButton.classList.add("d-none"); // Hide placeholder button
|
||||
submitButton.classList.remove("d-none"); // Show real submit button
|
||||
termsWarning.classList.add("d-none"); // Hide warning
|
||||
} else {
|
||||
fakeSubmitButton.classList.remove("d-none"); // Show placeholder button
|
||||
submitButton.classList.add("d-none"); // Hide real submit button
|
||||
}
|
||||
});
|
||||
|
||||
// Show warning if placeholder button is clicked
|
||||
fakeSubmitButton.addEventListener("click", function () {
|
||||
termsWarning.classList.remove("d-none"); // Show warning
|
||||
agreeCheckbox.focus(); // Focus on checkbox
|
||||
});
|
||||
});
|
||||
</script>
|
||||
25
CatherineLynwood/Views/Shared/Error.cshtml
Normal file
@ -0,0 +1,25 @@
|
||||
@model ErrorViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Error";
|
||||
}
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (Model.ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@Model.RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
115
CatherineLynwood/Views/Shared/_Layout.cshtml
Normal file
@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"] | Catherine Lynwood</title>
|
||||
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/css/fontawesome.min.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/css/brands.min.css" asp-append-version="true" />
|
||||
<link rel="stylesheet" href="~/css/duotone.min.css" asp-append-version="true" />
|
||||
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/apple-icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/apple-icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/apple-icon-72x72.png">
|
||||
<link rel="apple-touch-icon" sizes="76x76" href="/apple-icon-76x76.png">
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/apple-icon-114x114.png">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/apple-icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/apple-icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/apple-icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="192x192" href="/android-icon-192x192.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<meta name="msapplication-TileColor" content="#17C8EB">
|
||||
<meta name="msapplication-TileImage" content="/ms-icon-144x144.png">
|
||||
<meta name="theme-color" content="#17C8EB">
|
||||
|
||||
|
||||
|
||||
<link rel="alternate" type="application/rss+xml" title="Catherine Lynwood Blog Feed" href="https://www.catherinelynwood.com/feed">
|
||||
|
||||
<link rel="canonical" href="@($"https://www.catherinelynwood.com{Context.Request.Path}{Context.Request.QueryString}")" />
|
||||
|
||||
@RenderSection("Meta", required: false)
|
||||
|
||||
</head>
|
||||
<body class="bg-primary text-white">
|
||||
<div class="fixed-background"></div> <!-- Fixed background div -->
|
||||
|
||||
<div class="content">
|
||||
<!-- Content wrapper to keep everything on top of the background -->
|
||||
<header>
|
||||
<nav class="navbar fixed-top navbar-expand-sm navbar-toggleable-sm navbar-dark border-bottom border-2 border-primary box-shadow mb-3 bg-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Catherine Lynwood</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>
|
||||
</button>
|
||||
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
|
||||
<ul class="navbar-nav flex-grow-1">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-primary" asp-area="" asp-controller="Home" asp-action="AboutCatherineLynwood">About Catherine</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-primary" asp-area="" asp-controller="TheAlphaFlame" asp-action="Blog">Blog</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-primary" asp-area="" asp-controller="AskAQuestion" asp-action="Index">Ask Catherine</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle text-primary" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
The Alpha Flame
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="py-2"><a class="dropdown-item" asp-controller="TheAlphaFlame" asp-action="Index">Book Previews</a></li>
|
||||
@* <li><hr class="dropdown-divider"></li> *@
|
||||
<li class="py-2"><a class="dropdown-item" asp-controller="TheAlphaFlame" asp-action="Characters">Meet the Characters</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-primary" asp-area="" asp-controller="Home" asp-action="ContactCatherine">Contact Catherine</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="container-lg" style="margin-top: 70px;">
|
||||
<main role="main" class="pb-3">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
|
||||
|
||||
</div> <!-- End of content wrapper -->
|
||||
<footer class="border-top border-2 border-primary footer bg-dark">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center align-items-center" style="height: 140px;">
|
||||
<div class="col-12">
|
||||
<social-media-share title="Catherine Lynwood Blog" url="@Context.Request.Path" class="text-center m-3"></social-media-share>
|
||||
</div>
|
||||
<div class="col-12 text-center">
|
||||
<a class="text-light" asp-area="" asp-controller="Home" asp-action="ContactCatherine">Contact Catherine</a>
|
||||
</div>
|
||||
<div class="col-12 text-center text-light">
|
||||
© 2024 - Catherine Lynwood
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="~/lib/jquery/dist/jquery.min.js"></script>
|
||||
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="~/js/site.js" asp-append-version="true"></script>
|
||||
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
|
||||
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
|
||||
@RenderSection("Scripts", required: false)
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
48
CatherineLynwood/Views/Shared/_Layout.cshtml.css
Normal file
@ -0,0 +1,48 @@
|
||||
/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||
for details on configuring this project to bundle and minify static web assets. */
|
||||
|
||||
a.navbar-brand {
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0077cc;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.box-shadow {
|
||||
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
button.accept-policy {
|
||||
font-size: 1rem;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
line-height: 60px;
|
||||
}
|
||||
145
CatherineLynwood/Views/TheAlphaFlame/Beth.cshtml
Normal file
@ -0,0 +1,145 @@
|
||||
@{
|
||||
ViewData["Title"] = "Beth Fletcher";
|
||||
}
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">Meet The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Characters">Meet The Characters</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Beth</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Beth Fletcher</h1>
|
||||
<p>
|
||||
<em>The Alpha Flame:</em> Heroine
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="float-sm-start w-sm-50 p-3">
|
||||
<div id="carouselControls" class="carousel slide" data-bs-ride="carousel">
|
||||
<div class="carousel-inner">
|
||||
<div class="carousel-item active">
|
||||
<responsive-image src="beth-3.png" alt="Beth Fletcher" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="beth-6.png" alt="Beth Fletcher" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="beth-9.png" alt="Beth Fletcher" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="beth-10.png" alt="Beth Fletcher" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="beth-11.png" alt="Beth Fletcher" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="beth-12.png" alt="Beth Fletcher" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
</div>
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#carouselControls" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#carouselControls" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Beth Fletcher</h2>
|
||||
<p>I love the character of Beth. To me she's the most interesting of them all. Her harrowing back story and life of poverty and squaller shapes her character, yet through it all she still manages to maintain her personal standards.'</p>
|
||||
<h2 class="mb-3">Overview</h2>
|
||||
<p>Beth is a mystery girl, a woman whose life has been shaped by tragedy and resilience. Beth’s journey tells a tale of survival and quiet strength. Beth’s story is uniquely her own.</p>
|
||||
<p>Raised in challenging circumstances, her troubled past left her with scars, both seen and unseen. Yet, beneath the pain lies a woman with remarkable courage and determination. Beth is fiercely protective of those she loves, and her loyalty runs deeper than her struggles.</p>
|
||||
<p>Beth’s complexity is central to *The Alpha Flame*. She’s not just a survivor; she’s a fighter, finding ways to rise above her circumstances while navigating the dark shadows of her past.</p>
|
||||
<p>Through Beth, *The Alpha Flame* explores themes of redemption, forgiveness, and the unyielding strength of family ties. Her story is raw, emotional, and deeply human—a testament to the power of perseverance and love.</p>
|
||||
|
||||
<!-- Physical Description -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Physical Description</h2>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item"><strong>Hair:</strong> A dyed golden blonde hair.</li>
|
||||
<li class="list-group-item"><strong>Eyes:</strong> A sharp green that can pierce through lies or sparkle with mischief.</li>
|
||||
<li class="list-group-item"><strong>Height:</strong> Average, but she carries herself with a natural elegance that makes her seem taller.</li>
|
||||
<li class="list-group-item"><strong>Style:</strong> Usually wears clothes to go with “the job”.</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Personality Traits</h2>
|
||||
<div class="row d-flex">
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Resilient</h5>
|
||||
<p class="card-text">Beth’s strength lies in her ability to overcome the darkest moments of her life.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Loyal</h5>
|
||||
<p class="card-text">She stands by her loved ones with unshakable dedication, no matter the cost.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Compassionate</h5>
|
||||
<p class="card-text">Despite her struggles, Beth’s empathy for others shines through.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Key Themes</h2>
|
||||
<ul>
|
||||
<li>Survival and resilience in the face of adversity.</li>
|
||||
<li>The power of family bonds, especially between twins.</li>
|
||||
<li>Redemption and the quest for self-forgiveness.</li>
|
||||
<li>Strength through vulnerability and compassion.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Explore Beth Fletcher, from The Alpha Flame. Discover her intriguing story, complex personality, and the challenges she faces in this enthralling novel.">
|
||||
|
||||
<meta name="keywords" content="Beth Fletcher, The Alpha Flame, Catherine Lynwood, book characters, character backstory, identical twin, complex personality, captivating novel themes">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebPage",
|
||||
"name": "Beth Fletcher - The Alpha Flame",
|
||||
"description": "Explore Beth Fletcher, Maggie Grant's identical twin, from The Alpha Flame. Discover her intriguing story, complex personality, and the challenges she faces in this enthralling novel.",
|
||||
"url": "https://www.catherinelynwood.com/the-alpha-flame/characters/beth-fletcher",
|
||||
"author": {
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com"
|
||||
},
|
||||
"about": {
|
||||
"@@type": "CreativeWork",
|
||||
"name": "The Alpha Flame",
|
||||
"author": "Catherine Lynwood"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
260
CatherineLynwood/Views/TheAlphaFlame/Blog.cshtml
Normal file
@ -0,0 +1,260 @@
|
||||
@model CatherineLynwood.Models.BlogIndex
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Catherine's Blog";
|
||||
}
|
||||
|
||||
@* <style>
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
.card-img {
|
||||
filter: grayscale(80%);
|
||||
transition: filter 0.3s;
|
||||
}
|
||||
|
||||
.card:hover .card-img {
|
||||
filter: grayscale(0%);
|
||||
}
|
||||
</style> *@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-md">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">The Alpha Flame Blog</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>The Alpha Flame Blog</h1>
|
||||
<p>Follow along as Catherine Lynwood shares updates, reflections, and glimpses into the worlds and characters she is passionate about bringing to life.</p>
|
||||
</div>
|
||||
</div>
|
||||
<form asp-action="Blog" method="get" class="row border border-1 rounded-3 bg-white mx-0 mb-3">
|
||||
<div class="col-12 pt-3">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-2 col-md-10 col-lg-9 col-xl-8 col-xxl-7">
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text d-none d-sm-inline" id="basic-addon1">Sort by</span>
|
||||
|
||||
<select asp-for="BlogFilter.SortDirection" class="form-select js-submit-on-change" name="SortDirection" aria-label="Sort direction">
|
||||
<option value="1">Newest first</option>
|
||||
<option value="2">Oldest first</option>
|
||||
</select>
|
||||
<span class="input-group-text d-none d-sm-inline" id="basic-addon1">Results per page</span>
|
||||
<select asp-for="BlogFilter.ResultsPerPage" class="form-select js-submit-on-change" name="ResultsPerPage" aria-label="Results per page">
|
||||
<option value="10">10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="30">30</option>
|
||||
<option value="40">40</option>
|
||||
<option value="50">50</option>
|
||||
</select>
|
||||
|
||||
<button class="btn btn-dark" type="button" data-bs-toggle="collapse" data-bs-target="#categories">Show Categories</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden field for PageNumber -->
|
||||
<input type="hidden" asp-for="BlogFilter.PageNumber" value="@Model.BlogFilter.PageNumber" name="PageNumber" id="PageNumber" />
|
||||
<input type="hidden" asp-for="BlogFilter.TotalPages" value="@Model.BlogFilter.TotalPages" name="PreviousTotalPages" />
|
||||
|
||||
<div class="col-12 @(Model.ShowAdvanced ? "collapse show" : "collapse")" id="categories">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<ul class="list-inline">
|
||||
@foreach (var item in Model.BlogCategories)
|
||||
{
|
||||
<li class="list-inline-item p-1">
|
||||
<div class="form-check form-switch text-dark">
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
id="switch-@item.CategoryID"
|
||||
name="Categories"
|
||||
value="@item.CategoryID"
|
||||
checked="@item.Selected">
|
||||
<label class="form-check-label" for="switch-@item.CategoryID">
|
||||
@item.Category
|
||||
</label>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-dark w-100" type="submit">Apply Category Filter</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@if (Model.BlogFilter.TotalPages > 1)
|
||||
{
|
||||
<div class="col-12">
|
||||
<!-- Pagination Component -->
|
||||
<nav aria-label="Page navigation" class="d-flex justify-content-center mt-3">
|
||||
<ul class="pagination">
|
||||
<!-- Previous button -->
|
||||
<li class="page-item @(Model.BlogFilter.PageNumber <= 1 ? "disabled" : "")">
|
||||
<a class="page-link" href="javascript:void(0);" onclick="setPageNumber(@(Model.BlogFilter.PageNumber - 1))" aria-label="Previous">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<!-- Page numbers -->
|
||||
@for (int i = 1; i <= Model.BlogFilter.TotalPages; i++)
|
||||
{
|
||||
<li class="page-item @(Model.BlogFilter.PageNumber == i ? "active" : "")">
|
||||
<a class="page-link" href="javascript:void(0);" onclick="setPageNumber(@i)">@i</a>
|
||||
</li>
|
||||
}
|
||||
|
||||
<!-- Next button -->
|
||||
<li class="page-item @(Model.BlogFilter.PageNumber >= Model.BlogFilter.TotalPages ? "disabled" : "")">
|
||||
<a class="page-link" href="javascript:void(0);" onclick="setPageNumber(@(Model.BlogFilter.PageNumber + 1))" aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
}
|
||||
|
||||
</form>
|
||||
|
||||
@if (Model.IsMobile)
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var item in Model.Blogs)
|
||||
{
|
||||
<div class="col-12 mb-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body d-flex align-items-center">
|
||||
<div class="row w-100">
|
||||
<div class="col-4">
|
||||
<a asp-controller="TheAlphaFlame" asp-action="BlogItem" asp-route-slug="@item.BlogUrl" class="me-3">
|
||||
<responsive-image src="@item.ImageUrl" alt="@item.ImageAlt" class="img-fluid" display-width-percentage="30" style="width: 100%; height: auto;"></responsive-image>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<h2 class="h5 card-title mb-1">@item.Title</h2>
|
||||
<small class="text-muted d-block mb-2">@item.SubTitle</small>
|
||||
<div class="card-text mb-2" style="max-height: 120px; overflow-y: auto;">
|
||||
@Html.Raw(item.IndexText)
|
||||
</div>
|
||||
<a asp-controller="TheAlphaFlame" asp-action="BlogItem" asp-route-slug="@item.BlogUrl" class="text-dark float-end">Read more...</a>
|
||||
<div class="text-muted mt-2">@item.PublishDate.ToString("dd MMMM yyyy")</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var item in Model.Blogs)
|
||||
{
|
||||
|
||||
<div class="col-sm-6 col-md-4 mb-4 d-flex">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header text-center">
|
||||
<h2 class="h5 card-title">@item.Title</h2>
|
||||
<small class="text-muted">@item.SubTitle</small>
|
||||
</div>
|
||||
<a asp-controller="TheAlphaFlame" asp-action="BlogItem" asp-route-slug="@item.BlogUrl">
|
||||
<responsive-image src="@item.ImageUrl" alt="@item.ImageAlt" class="card-img rounded-0" display-width-percentage="30"></responsive-image>
|
||||
</a>
|
||||
<div class="card-body">
|
||||
<p class="card-text">@Html.Raw(item.IndexText)</p>
|
||||
<p class="float-end"><a asp-controller="TheAlphaFlame" asp-action="BlogItem" asp-route-slug="@item.BlogUrl" class="text-primary-emphasis">Read more...</a></p>
|
||||
<h6>@item.PublishDate.ToString("dd MMMM yyyy")</h6>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Model.Blogs.Count == 0)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p>No blog posts found.</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Explore the blog of Catherine Lynwood, author of *The Alpha Flame*, for updates, insights, and behind-the-scenes stories. Follow Catherine’s journey as a writer, discover inspiration for her novels, and delve into discussions on women’s fiction, mystery, and family secrets.">
|
||||
<meta name="keywords" content="Catherine Lynwood blog, The Alpha Flame updates, women’s fiction blog, author Catherine Lynwood insights, mystery novel blog, family secrets stories, strong female protagonists, 1980s fiction blog, book updates and stories, author’s journey blog, Catherine Lynwood writing process">
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
@{
|
||||
var blogPostsJson = string.Join(",", Model.Blogs.Select(item => $@"
|
||||
{{
|
||||
""@type"": ""BlogPosting"",
|
||||
""headline"": ""{item.Title}"",
|
||||
""datePublished"": ""{item.PublishDate:yyyy-MM-dd}"",
|
||||
""author"": {{
|
||||
""@type"": ""Person"",
|
||||
""name"": ""Catherine Lynwood""
|
||||
}},
|
||||
""description"": ""{item.IndexText}"",
|
||||
""url"": ""{item.BlogUrl}"",
|
||||
""image"": {{
|
||||
""@type"": ""ImageObject"",
|
||||
""url"": ""{item.ImageUrl}"",
|
||||
""description"": ""{item.ImageDescription}""
|
||||
}}
|
||||
}}"));
|
||||
|
||||
var jsonLdSchema = $@"
|
||||
{{
|
||||
""@context"": ""https://schema.org"",
|
||||
""@type"": ""Blog"",
|
||||
""name"": ""Catherine Lynwood’s Blog"",
|
||||
""description"": ""Catherine Lynwood shares updates, insights, and stories behind her novel *The Alpha Flame* and her writing journey."",
|
||||
""url"": ""https://www.catherinelynwood.com/the-alpha-flame/blog"",
|
||||
""blogPost"": [{blogPostsJson}]
|
||||
}}";
|
||||
}
|
||||
|
||||
<script type="application/ld+json">
|
||||
@Html.Raw(jsonLdSchema)
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@section Scripts {
|
||||
<script>
|
||||
function setPageNumber(pageNumber) {
|
||||
// Set the hidden PageNumber input value
|
||||
document.getElementById('PageNumber').value = pageNumber;
|
||||
|
||||
// Submit the form
|
||||
document.querySelector('form').submit();
|
||||
}
|
||||
|
||||
$(document).on('change', '.js-submit-on-change', function(e){
|
||||
$(this).closest('form').submit();
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
}
|
||||
96
CatherineLynwood/Views/TheAlphaFlame/Chapter1.cshtml
Normal file
@ -0,0 +1,96 @@
|
||||
@{
|
||||
ViewData["Title"] = "The Alpha Flame | Chapter 1 Excerpt";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Chapter 1 Excerpt</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="fw-bold">The Alpha Flame - Chapter 1 Excerpt</h1>
|
||||
<p>An exclusive glimpse into Beth's story</p>
|
||||
<p>
|
||||
An excerpt from Chapter 1 of The Alpha Flame, titled "Drowning in Silence - Beth."
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Excerpt Content -->
|
||||
<div class="row gx-5">
|
||||
<!-- Scene Image -->
|
||||
<div class="col-lg-5 mb-4 mb-lg-0">
|
||||
<responsive-image src="beth-12.png" alt="Scene from Beth's story" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="50"></responsive-image>
|
||||
</div>
|
||||
|
||||
<!-- Audio and Text -->
|
||||
<div class="col-lg-7">
|
||||
<div class="bg-white rounded-5 border border-3 border-dark shadow-lg p-3">
|
||||
<!-- Audio Player -->
|
||||
<div class="audio-player text-center">
|
||||
<audio controls>
|
||||
<source src="/audio/chapter-1-beth-excerpt.mp3" type="audio/mpeg">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
<!-- Text Content -->
|
||||
<div class="chapter-text">
|
||||
<p class="chapter-title">Drowning in Silence - Beth</p>
|
||||
<p>
|
||||
<em>I’d never known silence like that before. The kind that creeps under your skin and settles in your bones, sinking in so deep it feels like it might smother you. When I opened the door, that silence wrapped itself around me, choking me, filling me up until there was nothing else. I didn’t even know what I was seeing at first. I think maybe my mind tried to protect me, tried to shield me from what was right in front of me, even though I knew, deep down, that everything was about to change.</em>
|
||||
</p>
|
||||
<p>
|
||||
<em>She was slumped there in the bath, water cold and still around her, her face as blank as a wax doll’s, skin washed out, lifeless. The first thought I had, the thing I’ll never forgive myself for, was how wrong it looked. It felt surreal, like a trick. This wasn’t her. It couldn’t be. My mum wasn’t a drinker, not like this, not ever, but there was an empty bottle lying on its side beside the bath, rolling slightly as I opened the door wider. It felt like it was mocking me, daring me to believe what I was seeing.</em>
|
||||
</p>
|
||||
<p>
|
||||
<em>I felt sick, my throat clenching, my stomach twisting, and for a moment, I hated her, or whoever had done this to her. Hated the absurdity, the impossibility of it. She’d never have chosen that bottle over me, over herself. And yet there it was, an empty accusation, staring at me from the floor, her face pale and her lips blue. I couldn’t make sense of it. I just stood there, a dead thing staring back at her, just as lifeless as she was.</em>
|
||||
</p>
|
||||
<p>
|
||||
<em>They say your life flashes before your eyes when you die, but I think they’re wrong. I think it’s the people left behind, the ones who have to see it, who have to stand there, watching their entire world collapse around them. I saw everything; all the tiny pieces of a life she’d held together for me, every smile, every reassuring word, every single thing that had kept me safe. And I realised, right then, that I was all alone. Utterly and completely alone.</em>
|
||||
</p>
|
||||
<p>
|
||||
<em>There’s something that breaks in you when you lose everything in one heartbeat. It’s like the walls inside you just give way, crumbling into nothing, until all that’s left is this empty shell. I felt it, that shattering, like glass splintering into a million pieces inside my chest. I remember gripping the doorframe so hard my knuckles turned white, the pain grounding me, keeping me from slipping into whatever dark pit was opening up beneath my feet. I couldn’t look away from her. I couldn’t move, couldn’t breathe. I was frozen, trapped in this nightmare that wouldn’t end, a part of me hoping that if I stared long enough, I’d wake up. That this would all just go away.</em>
|
||||
</p>
|
||||
<p>
|
||||
<em>But it didn’t. And I knew it wouldn’t. Because that was the moment my life ended too. She may have been the one in the water, but I was drowning right along with her...</em>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Dive into Chapter 1 of 'The Alpha Flame' by Catherine Lynwood. Follow Beth's journey in a gripping tale of mystery, family, and resilience set in the 1980s." />
|
||||
<meta name="keywords" content="The Alpha Flame, Chapter 1, Beth, Catherine Lynwood, mystery novel, 1980s fiction, family drama, book chapters, gripping novels, fiction by Catherine Lynwood" />
|
||||
<meta name="author" content="Catherine Lynwood" />
|
||||
|
||||
<MetaTag meta-title="Chapter 1: Beth - The Alpha Flame by Catherine Lynwood"
|
||||
meta-description="Explore Chapter 1 of 'The Alpha Flame' by Catherine Lynwood. Discover Maggie's captivating story, full of determination and secrets, set in the vivid 1980s."
|
||||
meta-keywords="The Alpha Flame, Chapter 1, Maggie, Catherine Lynwood, 1980s fiction, family secrets, strong female characters, captivating novels, fiction by Catherine Lynwood"
|
||||
meta-author="Catherine Lynwood"
|
||||
meta-url="https://www.catherinelynwood.com/the-alpha-flame/chapters/chapter-1-beth"
|
||||
meta-image="https://www.catherinelynwood.com/images/webp/beth-12-600-600.webp"
|
||||
meta-image-alt="Beth from 'The Alpha Flame' by Catherine Lynwood"
|
||||
og-site-name="Catherine Lynwood - The Alpha Flame"
|
||||
article-published-time="@new DateTime(2024,11,20)"
|
||||
article-modified-time="@new DateTime(2024,11,20)"
|
||||
twitter-card-type="player"
|
||||
twitter-site-handle="@@CathLynwood"
|
||||
twitter-creator-handle="@@CathLynwood"
|
||||
twitter-player-width="480"
|
||||
twitter-player-height="80" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
100
CatherineLynwood/Views/TheAlphaFlame/Chapter2.cshtml
Normal file
@ -0,0 +1,100 @@
|
||||
@{
|
||||
ViewData["Title"] = "The Alpha Flame | Chapter 2 Excerpt";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Chapter 2 Excerpt</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="fw-bold">The Alpha Flame - Chapter 2 Excerpt</h1>
|
||||
<p>An exclusive glimpse into Maggie's story</p>
|
||||
<p>
|
||||
An excerpt from Chapter 2 of The Alpha Flame, titled "The Last Lesson - Maggie."
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Excerpt Content -->
|
||||
<div class="row gx-5">
|
||||
<!-- Scene Image -->
|
||||
<div class="col-lg-5 mb-4 mb-lg-0">
|
||||
<responsive-image src="maggie-grant-43.png" alt="Scene from Maggie's story" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="50"></responsive-image>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Audio and Text -->
|
||||
<div class="col-lg-7">
|
||||
<div class="bg-white rounded-5 border border-3 border-dark shadow-lg p-3">
|
||||
<!-- Audio Player -->
|
||||
<div class="audio-player text-center">
|
||||
<audio controls>
|
||||
<source src="/audio/chapter-2-maggie-excerpt.mp3" type="audio/mpeg">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Text Content -->
|
||||
<div class="chapter-text">
|
||||
<p class="chapter-title">The Last Lesson - Maggie</p>
|
||||
<p><em>There was a knock at the door.</em></p>
|
||||
<p>“Colin’s here,” called mum.</p>
|
||||
<p>“Okay, I’ll be there in a second,” I replied.</p>
|
||||
<p><em>I was so nervous. I wasn’t normally the nervous type, but today was important. Who would book their driving test on Christmas Eve for God’s sake, I must be crazy. It’s going to be manic out there,</em> I thought to myself, <em>trying to stay calm.</em></p>
|
||||
<p><em>I checked my look in the mirror: Hair – yes perfect, makeup – spot on, my shirt – white and business like, possibly a bit thin but necessarily so, skirt – perfect length, stockings – I always wore stockings, shoes – practical.</em> <em>Okay let’s do this</em>, I thought.</p>
|
||||
<p>“Maggie,” called Mum, “you’re going to be late.”</p>
|
||||
<p>“Coming.”</p>
|
||||
<p><em>I walked into the hallway where mum was talking to Colin.</em></p>
|
||||
<p>“How do I look?” I asked.</p>
|
||||
<p>Mum looked at me. “No bra?” she queried.</p>
|
||||
<p><em>I just looked at her, and mum smiled in response.</em></p>
|
||||
<p>Colin ran his eyes up and down my body. “Wow, you scrub up well,” he said.</p>
|
||||
<p>“Good luck, Sweetie,” said Mum.</p>
|
||||
<p><em>Colin led the way to the car. I don’t know why, it’s not like it was my first lesson. Hopefully, it was my last. We arrived at the car and even more bizarrely he opened the driver’s door for me.</em></p>
|
||||
<p>“Thank you,” I said. “What’s with the chivalry?”</p>
|
||||
<p>“No reason,” he replied, scurrying around to the passenger side and getting into the front seat.</p>
|
||||
<p><em>I started performing all my learner checks, seat belt, mirror, all that bosh, then started the car and put it into reverse.</em></p>
|
||||
<p>“Just take your time,” said Colin.</p>
|
||||
<p><em>As if totally ignoring him, I revved the engine far too fast and slipped my foot off the clutch. The car leapt backwards in a tight right-hand arc.</em></p>
|
||||
<p>“Jesus!” exclaimed Colin. “What are you doing?” as he stamped on the brake pedal.</p>
|
||||
<p>“Sorry, I’m rather nervous,” I said, looking over my shoulder at Mum, who was stood waving.</p>
|
||||
<p><em>Colin looked at me closely, clearly wondering why the cool, calm, and collected girl he’d been teaching to drive for the past four months was suddenly driving like a complete idiot. I didn’t know what had come over me. I had been waiting for this day for so long and was so ready, but for some reason I was shaking.</em> <em>Pull yourself together</em>, I thought.</p>
|
||||
<p>Calmly, he said, “Don’t worry. Let’s just try that again...”</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Explore Chapter 2 of 'The Alpha Flame' by Catherine Lynwood. Discover Maggie's captivating story, full of determination and secrets, set in the vivid 1980s." />
|
||||
<meta name="keywords" content="The Alpha Flame, Chapter 2, Maggie, Catherine Lynwood, 1980s fiction, family secrets, strong female characters, captivating novels, fiction by Catherine Lynwood" />
|
||||
<meta name="author" content="Catherine Lynwood" />
|
||||
|
||||
<MetaTag meta-title="Chapter 2: Maggie - The Alpha Flame by Catherine Lynwood"
|
||||
meta-description="Explore Chapter 2 of 'The Alpha Flame' by Catherine Lynwood. Discover Maggie's captivating story, full of determination and secrets, set in the vivid 1980s."
|
||||
meta-keywords="The Alpha Flame, Chapter 2, Maggie, Catherine Lynwood, 1980s fiction, family secrets, strong female characters, captivating novels, fiction by Catherine Lynwood"
|
||||
meta-author="Catherine Lynwood"
|
||||
meta-url="https://www.catherinelynwood.com/the-alpha-flame/chapters/chapter-2-maggie"
|
||||
meta-image="https://www.catherinelynwood.com/images/webp/maggie-grant-43-600.webp"
|
||||
meta-image-alt="Maggie from 'The Alpha Flame' by Catherine Lynwood"
|
||||
og-site-name="Catherine Lynwood - The Alpha Flame"
|
||||
article-published-time="@new DateTime(2024,11,20)"
|
||||
article-modified-time="@new DateTime(2024,11,20)"
|
||||
twitter-card-type="player"
|
||||
twitter-site-handle="@@CathLynwood"
|
||||
twitter-creator-handle="@@CathLynwood"
|
||||
twitter-player-width="480"
|
||||
twitter-player-height="80" />
|
||||
|
||||
|
||||
}
|
||||
141
CatherineLynwood/Views/TheAlphaFlame/Characters.cshtml
Normal file
@ -0,0 +1,141 @@
|
||||
@*
|
||||
For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
|
||||
*@
|
||||
@{
|
||||
}
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Meet The Charaters";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Meet The Characters</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1 class="text-center mb-4">Meet the Characters</h1>
|
||||
<p>
|
||||
When creating a character, I typically start with a visual prompt. I look for a photo of someone who resembles the character I envision, using it as inspiration to craft their description. If I can't find the right match, I turn to AI to generate an image. I refine the prompts and make adjustments until the result aligns with the person I see in my mind’s eye. This image then serves as a reference throughout the writing process.
|
||||
</p>
|
||||
<p>
|
||||
I thought it would be fun to share these images with you and give you a little information about their character, but not too much as I don't want to give the plot away.
|
||||
</p>
|
||||
<p>
|
||||
The characters in <i>The Alpha Flame</i> are complex, multi-dimensional individuals with unique personalities, histories, and motivations. Each character has a distinct voice, a story to tell, and a role to play in the unfolding narrative. As the story progresses, these characters grow, change, and reveal new facets of themselves, adding depth and richness to the world of the novel.
|
||||
</p>
|
||||
<p>
|
||||
Explore the stories of the captivating characters in <i>The Alpha Flame</i>. Click on their thumbnails to learn more about each one.
|
||||
</p>
|
||||
<div class="row g-4">
|
||||
<!-- Maggie -->
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a asp-action="Maggie" class="text-decoration-none">
|
||||
<div class="character-card">
|
||||
<responsive-image src="maggie-grant.png" alt="Maggie Grant" class="character-thumbnail" display-width-percentage="30"></responsive-image>
|
||||
<p class="character-name">Maggie Grant</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Beth -->
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a asp-action="Beth" class="text-decoration-none">
|
||||
<div class="character-card">
|
||||
<responsive-image src="beth-3.png" alt="Beth Fletcher" class="character-thumbnail" display-width-percentage="30"></responsive-image>
|
||||
<p class="character-name">Beth Fletcher</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Rosie -->
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a asp-action="Rosie" class="text-decoration-none">
|
||||
<div class="character-card">
|
||||
<responsive-image src="rosie-macdonald-2.png" alt="Rosie MacDonald" class="character-thumbnail" display-width-percentage="30"></responsive-image>
|
||||
<p class="character-name">Rosie MacDonald</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Rob -->
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a asp-action="Rob" class="text-decoration-none">
|
||||
<div class="character-card">
|
||||
<responsive-image src="rob-jackson-2.png" alt="Rob Jackson" class="character-thumbnail" display-width-percentage="30"></responsive-image>
|
||||
<p class="character-name">Rob Jackson</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Zoe -->
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a asp-action="Zoe" class="text-decoration-none">
|
||||
<div class="character-card">
|
||||
<responsive-image src="zoe-davies.png" alt="Zoe Davies" class="character-thumbnail" display-width-percentage="30"></responsive-image>
|
||||
<p class="character-name">Zoe Davies</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Rick -->
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a asp-action="Rick" class="text-decoration-none">
|
||||
<div class="character-card">
|
||||
<responsive-image src="rick.png" alt="Rick" class="character-thumbnail" display-width-percentage="30"></responsive-image>
|
||||
<p class="character-name">Rick</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Rebecca -->
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a asp-action="Rebecca" class="text-decoration-none">
|
||||
<div class="character-card">
|
||||
<responsive-image src="rebecca-jones.png" alt="Rebecca Jones" class="character-thumbnail" display-width-percentage="30"></responsive-image>
|
||||
<p class="character-name">Rebecca Jones</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<!-- Sophie -->
|
||||
<div class="col-6 col-md-4 col-lg-3">
|
||||
<a asp-action="Sophie" class="text-decoration-none">
|
||||
<div class="character-card">
|
||||
<responsive-image src="sophie-jones.png" alt="Sophie Jones" class="character-thumbnail" display-width-percentage="30"></responsive-image>
|
||||
<p class="character-name">Sophie Jones</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Meet the compelling characters of The Alpha Flame by Catherine Lynwood. Discover their stories, relationships, and the roles they play in this captivating novel.">
|
||||
|
||||
<meta name="keywords" content="The Alpha Flame, Catherine Lynwood, book characters, character backstories, novel themes, strong female leads, gripping stories, Maggie Grant, Beth Fletcher, Rob Jackson, Zoe Davies, Rosie MacDonald, Rebecca Jones, Sophie Jones, Rick">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebPage",
|
||||
"name": "Meet the Characters - The Alpha Flame",
|
||||
"description": "Meet the compelling characters of The Alpha Flame by Catherine Lynwood. Discover their stories, relationships, and the roles they play in this captivating novel.",
|
||||
"url": "https://www.catherinelynwood.com/the-alpha-flame/characters",
|
||||
"author": {
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com"
|
||||
},
|
||||
"about": {
|
||||
"@@type": "CreativeWork",
|
||||
"name": "The Alpha Flame",
|
||||
"author": "Catherine Lynwood"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
198
CatherineLynwood/Views/TheAlphaFlame/DefaultTemplate.cshtml
Normal file
@ -0,0 +1,198 @@
|
||||
@model CatherineLynwood.Models.Blog
|
||||
@using System.Text.RegularExpressions
|
||||
|
||||
@{
|
||||
ViewData["Title"] = $"{Model.Title}";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<partial name="_Breadcrumb" model="Model" />
|
||||
</div>
|
||||
</div>
|
||||
<article>
|
||||
@if (Model.ShowThanks)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-success alert-dismissible fade show">
|
||||
<strong class="alert-heading">Success!</strong><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button><br />
|
||||
<p>
|
||||
Your comments have been submitted and are awaiting approval. Thank you for your feedback.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<header>
|
||||
<h1 class="d-inline-block">@Model.Title</h1> <h2 class="h1 d-inline-block">@Model.SubTitle</h2>
|
||||
<p>Posted on @Model.PublishDate.ToString("MMMM d, yyyy") by Catherine Lynwood</p>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.AudioTranscriptUrl))
|
||||
{
|
||||
<div class="row justify-content-center p-3">
|
||||
<div class="col-auto">
|
||||
<audio controls>
|
||||
<source src="/audio/@Model.AudioTranscriptUrl" type="audio/mpeg">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@if (!string.IsNullOrWhiteSpace(Model.ImageUrl) && !string.IsNullOrWhiteSpace(Model.ContentTop))
|
||||
{
|
||||
@if (Model.ImageFirst)
|
||||
{
|
||||
<div class="float-md-start w-md-50 p-3">
|
||||
<figure>
|
||||
<responsive-image src="@Model.ImageUrl" alt="@Model.ImageAlt" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="50"></responsive-image>
|
||||
<figcaption>@Html.Raw(Model.ImageDescription)</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Model.ContentTop))
|
||||
{
|
||||
<div>@Html.Raw(Model.ContentTop)</div>
|
||||
}
|
||||
@if (!Model.ImageFirst)
|
||||
{
|
||||
<div class="float-md-end w-md-50 p-3">
|
||||
<figure>
|
||||
<responsive-image src="@Model.ImageUrl" alt="@Model.ImageAlt" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="50"></responsive-image>
|
||||
<figcaption>@Html.Raw(Model.ImageDescription)</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(Model.ContentTop))
|
||||
{
|
||||
@Html.Raw(Model.ContentTop)
|
||||
}
|
||||
|
||||
else if (!string.IsNullOrWhiteSpace(Model.ImageUrl))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12 p-3">
|
||||
<figure>
|
||||
<responsive-image src="@Model.ImageUrl" alt="@Model.ImageAlt" class="img-fluid rounded-5 border border-3 border-dark shadow-lg"></responsive-image>
|
||||
<figcaption>@Html.Raw(Model.ImageDescription)</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Model.AudioTeaserUrl))
|
||||
{
|
||||
<div class="row justify-content-center bg-white text-dark rounded-3 p-3">
|
||||
<div class="col-auto">
|
||||
<audio controls>
|
||||
<source src="/audio/@Model.AudioTeaserUrl" type="audio/mpeg">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.AudioTeaserText))
|
||||
{
|
||||
<div class="col-12" style="max-height: 120px; overflow-y: auto;">
|
||||
@Html.Raw(Model.AudioTeaserText)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(Model.ContentBottom))
|
||||
{
|
||||
@Html.Raw(Model.ContentBottom)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@await Component.InvokeAsync("BlogCommentComponent", new { BlogID = Model.BlogID })
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Explore the blog of Catherine Lynwood, author of *The Alpha Flame*, for updates, insights, and behind-the-scenes stories. Follow Catherine’s journey as a writer, discover inspiration for her novels, and delve into discussions on women’s fiction, mystery, and family secrets.">
|
||||
<meta name="keywords" content="Catherine Lynwood blog, The Alpha Flame updates, women’s fiction blog, author Catherine Lynwood insights, mystery novel blog, family secrets stories, strong female protagonists, 1980s fiction blog, book updates and stories, author’s journey blog, Catherine Lynwood writing process">
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
@functions {
|
||||
public static string StripHtml(string input)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(input) ? string.Empty : Regex.Replace(input, "<.*?>", string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@{
|
||||
string blogUrl = $"https://www.catherinelynwood.com/{Model.BlogUrl}";
|
||||
string imageUrl = $"https://www.catherinelynwood.com/images/webp/{Model.DefaultWebpImage}";
|
||||
string description = $"Listen to my new blog entry: {Model.Title} by Catherine Lynwood";
|
||||
|
||||
var contentTopPlainText = StripHtml(Model.ContentTop);
|
||||
var contentBottomPlainText = StripHtml(Model.ContentBottom);
|
||||
|
||||
var blogPostJsonLd = $@"
|
||||
{{
|
||||
""@context"": ""https://schema.org"",
|
||||
""@type"": ""BlogPosting"",
|
||||
""headline"": ""{Model.Title}"",
|
||||
""datePublished"": ""{Model.PublishDate:yyyy-MM-dd}"",
|
||||
""author"": {{
|
||||
""@type"": ""Person"",
|
||||
""name"": ""Catherine Lynwood""
|
||||
}},
|
||||
""description"": ""{Model.IndexText}"",
|
||||
""articleBody"": ""{contentTopPlainText} {contentBottomPlainText}"",
|
||||
""url"": ""https://www.catherinelynwood.com/the-alpah-flame/blog/{Model.BlogUrl}"",
|
||||
""image"": {{
|
||||
""@type"": ""ImageObject"",
|
||||
""url"": ""https://www.catherinelynwood.com/images/webp/{Model.DefaultWebpImage}"",
|
||||
""description"": ""{Model.ImageDescription}""
|
||||
}},
|
||||
""interactionStatistic"": {{
|
||||
""@type"": ""InteractionCounter"",
|
||||
""interactionType"": {{
|
||||
""@type"": ""http://schema.org/LikeAction""
|
||||
}},
|
||||
""userInteractionCount"": {Model.Likes}
|
||||
}}
|
||||
}}";
|
||||
}
|
||||
|
||||
<meta property="og:title" content="The Alpha Flame by Catherine Lynwood" />
|
||||
<meta property="og:description" content="@Model.IndexText" />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:url" content="@blogUrl" />
|
||||
<meta property="og:image" content="@imageUrl" />
|
||||
<meta property="og:image:alt" content="@Model.ImageAlt" />
|
||||
<meta property="og:site_name" content="Catherine Lynwood - The Alpha Flame" />
|
||||
<meta property="article:author" content="https://www.catherinelynwood.com" />
|
||||
<meta property="article:published_time" content="2024-11-19" />
|
||||
<meta property="article:modified_time" content="2024-11-19" />
|
||||
|
||||
<meta name="twitter:card" content="player">
|
||||
<meta name="twitter:title" content="@Model.Title" />
|
||||
<meta name="twitter:description" content="@description">
|
||||
<meta name="twitter:site" content="@@CathLynwood" />
|
||||
<meta name="twitter:creator" content="@@CathLynwood" />
|
||||
<meta name="twitter:image" content="@imageUrl" />
|
||||
<meta name="twitter:image:alt" content="@Model.ImageAlt" />
|
||||
<meta name="twitter:player" content="@blogUrl">
|
||||
<meta name="twitter:player:width" content="480">
|
||||
<meta name="twitter:player:height" content="80">
|
||||
|
||||
<script type="application/ld+json">
|
||||
@Html.Raw(blogPostJsonLd)
|
||||
</script>
|
||||
|
||||
}
|
||||
|
||||
31
CatherineLynwood/Views/TheAlphaFlame/FrontCover.cshtml
Normal file
@ -0,0 +1,31 @@
|
||||
@{
|
||||
ViewData["Title"] = "The Alpha Flame | Front Cover";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Front Cover</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<section id="cover" class="mb-5">
|
||||
<div class="card">
|
||||
<responsive-image src="the-alpha-flame-7.png" class="card-img-top" alt="The Alpha Flame Cover" display-width-percentage="50"></responsive-image>
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Front Cover Draft</h2>
|
||||
<p class="card-text">
|
||||
Welcome to the first glimpse of The Alpha Flame! What you see here is a draft version of the cover as it continues to evolve. As the story itself unfolds, so does the cover design, and while this is just one concept, it brings the title to life visually, hinting at the strength and intrigue within its pages. Please keep in mind that this is an early version; the final design may change as I refine both the visual elements and the story itself.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
125
CatherineLynwood/Views/TheAlphaFlame/Index.cshtml
Normal file
@ -0,0 +1,125 @@
|
||||
@{
|
||||
ViewData["Title"] = "The Alpha Flame";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">The Alpha Flame</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
|
||||
|
||||
<!-- Chapter Previews -->
|
||||
<section id="chapters">
|
||||
<h2>Book Previews</h2>
|
||||
<div class="row mt-3">
|
||||
<!-- Chapter 1 Preview -->
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 character-card">
|
||||
<a asp-action="FrontCover">
|
||||
<responsive-image src="the-alpha-flame-7.png" class="card-img-top" alt="Beth Fletcher in her flat" display-width-percentage="50"></responsive-image>
|
||||
</a>
|
||||
<div class="card-body border-top border-3 border-dark">
|
||||
<h3 class="card-title">The Front Cover</h3>
|
||||
<p class="card-text">In the quieter moments when I'm not writing I've been spending some time working on the cover of my book. It's definitely early days at the moment, but you can take a look at a draft version of the front cover if you wish.'</p>
|
||||
<div class="text-end"><a asp-action="FrontCover" class="btn btn-dark">Read More</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Chapter 1 Preview -->
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 character-card">
|
||||
<a asp-action="Chapter1">
|
||||
<responsive-image src="beth-13.png" class="card-img-top" alt="Beth Fletcher in her flat" display-width-percentage="50"></responsive-image>
|
||||
</a>
|
||||
<div class="card-body border-top border-3 border-dark">
|
||||
<h3 class="card-title">Chapter 1: Drowning in Silence - Beth</h3>
|
||||
<p class="card-text">Beth returns home to find her mother lifeless in the bath, an empty bottle beside her. Overwhelmed by grief and disbelief, she wrestles with feelings of abandonment and anger as her world shatters around her. Left alone at just sixteen, Beth faces the bleak realities of life without her mother, including navigating the cold and impersonal system of social services. When her estranged aunt briefly steps in before leaving her completely alone, Beth is forced to make a desperate decision—to run away and forge her own path, no matter the cost.</p>
|
||||
<div class="text-end"><a asp-action="Chapter1" class="btn btn-dark">Read More</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Chapter 2 Preview -->
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card h-100 character-card">
|
||||
<a asp-action="Chapter2">
|
||||
<responsive-image src="maggie-grant-44.png" class="card-img-top" alt="Maggie Grant, The Alpha Flame" display-width-percentage="50"></responsive-image>
|
||||
</a>
|
||||
<div class="card-body border-top border-3 border-dark">
|
||||
<h3 class="card-title">Chapter 2: The Last Lesson - Maggie</h3>
|
||||
<p class="card-text">On Christmas Eve, Maggie nervously heads out for her driving test, determined to succeed. Dressed to impress and brimming with playful confidence, she charms her instructor Colin and even turns heads at the test center. However, an unexpected incident involving a child running into the road tests her composure and skill. Maggie’s quick thinking earns her a perfect pass, and she celebrates with her family and best friend, Rosie. Driving her newly restored Triumph TR6 for the first time, Maggie revels in her freedom but can’t shake the haunting image of a lonely girl she spots under a flyover, sparking a lingering sense of unease.</p>
|
||||
<div class="text-end"><a asp-action="Chapter2" class="btn btn-dark">Read More</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 mb-4 px-4">
|
||||
<div class="row rounded-5 border border-3 border-dark overflow-hidden">
|
||||
<div class="col-md-4 p-0 responsive-border border-3 border-dark">
|
||||
<a asp-action="Characters">
|
||||
<responsive-image src="maggie-grant-47.png" class="fit-image" alt="Maggie Grant, The Alpha Flame" display-width-percentage="50"></responsive-image>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-8 bg-light text-dark p-3">
|
||||
<h3 class="card-title">Meet the Characters</h3>
|
||||
<div class="card-text">
|
||||
<p>Dive into the vibrant world of The Alpha Flame and discover the unforgettable cast of characters that bring this story to life.</p>
|
||||
|
||||
<p>Get to know Maggie, the fiery and magnetic heart of the tale, and Beth, whose troubled past contrasts with her unyielding strength. Meet Rosie, Maggie's best friend, who brings warmth and humor to every scene, and Rebecca, a steady presence with a hidden depth.</p>
|
||||
|
||||
<p>Explore the mind of Rob, the gadget-obsessed photographer whose quiet determination matches his deep connection to Maggie. Then there’s Zoe, the sharp-witted ex-policewoman who’s full of surprises.</p>
|
||||
|
||||
<p>Not everyone is a friend, though—Sophie brings tension and drama with her prickly demeanor, and Rick, the dark and dangerous antagonist, keeps the stakes high and the tension palpable.</p>
|
||||
|
||||
<p>Find out what makes them tick, what drives their actions, and what secrets they’re hiding. Who will you root for, and who will you love to hate? Discover it all in The Alpha Flame.</p>
|
||||
</div>
|
||||
<div class="text-end"><a asp-action="Characters" class="btn btn-dark">Read More</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Explore The Alpha Flame, a novel by Catherine Lynwood. Dive into the story of passion, love, and power with strong characters and compelling themes.">
|
||||
|
||||
<meta name="keywords" content="The Alpha Flame, Catherine Lynwood, novel, book cover, story of passion, love, power, strong characters, compelling themes">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebPage",
|
||||
"name": "The Alpha Flame - Cover Page",
|
||||
"description": "Explore The Alpha Flame, a novel by Catherine Lynwood. Dive into the story of passion, love, and power with strong characters and compelling themes.",
|
||||
"url": "https://www.catherinelynwood.com/the-alpha-flame",
|
||||
"author": {
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com"
|
||||
},
|
||||
"about": {
|
||||
"@@type": "CreativeWork",
|
||||
"name": "The Alpha Flame",
|
||||
"description": "A captivating novel about passion, love, and power."
|
||||
},
|
||||
"isPartOf": {
|
||||
"@@type": "WebSite",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
}
|
||||
185
CatherineLynwood/Views/TheAlphaFlame/Maggie.cshtml
Normal file
@ -0,0 +1,185 @@
|
||||
@{
|
||||
ViewData["Title"] = "Maggie Grant";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">Meet The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Characters">Meet The Characters</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Maggie Grant</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Maggie Grant</h1>
|
||||
<p>
|
||||
<em>The Alpha Flame:</em> Heroine
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="float-sm-end w-sm-50 p-3">
|
||||
<div id="carouselControls" class="carousel slide" data-bs-ride="carousel">
|
||||
<div class="carousel-inner">
|
||||
<div class="carousel-item active">
|
||||
<responsive-image src="maggie-grant.png" alt="Maggie Grant" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="maggie-grant-9.png" alt="Maggie Grant" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="maggie-grant-15.png" alt="Maggie Grant" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="maggie-grant-27.png" alt="Maggie Grant" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="maggie-grant-30.png" alt="Maggie Grant" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="maggie-grant-31.png" alt="Maggie Grant" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="maggie-grant-34.png" alt="Maggie Grant" class="d-block w-100 rounded-5 border border-3 border-dark overflow-hidden" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
</div>
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#carouselControls" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#carouselControls" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="mb-3">Overview</h2>
|
||||
<p>Maggie Grant is the fiery and complex heroine of <em>The Alpha Flame:</em>. With her striking red hair, sharp wit, and magnetic personality, she is a force to be reckoned with. Maggie’s journey is one of self-discovery, resilience, and dominance, as she navigates a world full of challenges, betrayal, and passion.</p>
|
||||
|
||||
<p>Born and raised in a modest town, Maggie’s life takes unexpected turns that shape her into the strong woman she becomes. Her natural charisma and keen intelligence make her a leader in any room, but it’s her unwavering determination that sets her apart. Maggie doesn’t just survive—she thrives, finding ways to take control of situations that would leave others defeated.</p>
|
||||
|
||||
<p>At the heart of her story lies her deep connection with her twin sister, Maggie. Their bond is more than familial; together, they are unstoppable. Maggie’s loyalty to Maggie and her fierce protection of those she loves are as much a part of her character as her fiery temper and unapologetic ambition. The relationship between these two women is a testament to the strength that comes from understanding and embracing their shared history, no matter how dark or painful it may be.</p>
|
||||
|
||||
<p>Maggie’s journey is not just about strength—it’s also about vulnerability. Beneath her confident exterior lies a woman grappling with the weight of her past and the choices she must make. Her character explores the balance between dominance and compassion, between power and intimacy. Maggie is not just a heroine; she is a deeply human character who reflects the complexities of life, love, and ambition.</p>
|
||||
|
||||
<p>Throughout <em>The Alpha Flame:</em>, Maggie’s love of cars, her sense of justice, and her drive to uncover the truth propel her forward. Whether she’s behind the wheel of her beloved Triumph TR6 or unraveling a family mystery, Maggie’s story is one of grit and grace, fire and finesse.</p>
|
||||
|
||||
<p>Discover the unforgettable Maggie Grant—a woman who refuses to be underestimated and always leaves an impression. Her story will inspire you, challenge you, and keep you turning the pages.</p>
|
||||
|
||||
<!-- Physical Description -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Physical Description</h2>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item"><strong>Hair:</strong> A rich, deep red that seems to match her passionate personality.</li>
|
||||
<li class="list-group-item"><strong>Eyes:</strong> A sharp green that can pierce through lies or sparkle with mischief.</li>
|
||||
<li class="list-group-item"><strong>Height:</strong> Average, but she carries herself with a natural elegance that makes her seem taller.</li>
|
||||
<li class="list-group-item"><strong>Style:</strong> Effortlessly chic, with a love for vintage-inspired fashion and a knack for designing her own clothes.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Personality Traits -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Personality Traits</h2>
|
||||
<div class="row d-flex">
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Charismatic</h5>
|
||||
<p class="card-text">Maggie has a natural magnetism that draws people in.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Witty</h5>
|
||||
<p class="card-text">Her sharp tongue ensures she’s never at a loss for words.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Loyal</h5>
|
||||
<p class="card-text">She’ll stand by your side when you need her the most.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- Interests and Hobbies -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Interests and Hobbies</h2>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-3">
|
||||
<i class="fad fa-pencil"></i> <strong>Fashion Design:</strong> Sketching dress ideas in her notebook.
|
||||
</li>
|
||||
<li class="mb-3">
|
||||
<i class="fad fa-car"></i> <strong>Classic Cars:</strong> Particularly her prized Triumph TR6.
|
||||
</li>
|
||||
<li>
|
||||
<i class="fad fa-music"></i> <strong>Piano:</strong> A hidden talent she rarely performs for others.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Quotes -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Quotes from Maggie</h2>
|
||||
<blockquote class="blockquote">
|
||||
<p>"Don’t mistake kindness for weakness—you’ll regret it."</p>
|
||||
</blockquote>
|
||||
<blockquote class="blockquote">
|
||||
<p>"If you’re going to do something, do it with style."</p>
|
||||
</blockquote>
|
||||
</section>
|
||||
|
||||
<!-- Did You Know -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Did You Know?</h2>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">Maggie has a soft spot for old movies and late-night drives by the coast.</li>
|
||||
<li class="list-group-item">Her nickname, "Rusty," was given to her by her brother as a playful nod to her red hair.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Discover Maggie Grant, the fiery heroine of The Alpha Flame. Learn about her character, backstory, and the themes that define her journey in this captivating novel.">
|
||||
|
||||
<meta name="keywords" content="Maggie Grant, The Alpha Flame, Catherine Lynwood, book characters, character backstory, fiery heroine, strong female lead, novel themes">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebPage",
|
||||
"name": "Maggie Grant - The Alpha Flame",
|
||||
"description": "Discover Maggie Grant, the fiery heroine of The Alpha Flame. Learn about her character, backstory, and the themes that define her journey in this captivating novel.",
|
||||
"url": "https://www.catherinelynwood.com/maggie-grant",
|
||||
"author": {
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com"
|
||||
},
|
||||
"about": {
|
||||
"@@type": "CreativeWork",
|
||||
"name": "The Alpha Flame",
|
||||
"author": "Catherine Lynwood"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
}
|
||||
131
CatherineLynwood/Views/TheAlphaFlame/Rebecca.cshtml
Normal file
@ -0,0 +1,131 @@
|
||||
@{
|
||||
ViewData["Title"] = "Rebecca Jones";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">Meet The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Characters">Meet The Characters</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Rebecca Jones</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<section class="hero-section mb-5">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Rebecca Jones</h1>
|
||||
<p>
|
||||
<em>The Alpha Flame:</em> The Loyal Confidant
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="float-sm-end w-sm-50 p-3">
|
||||
<responsive-image src="rebecca-jones.png" alt="Rebecca Jones" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<p>Rebecca Jones is Maggie's dependable second-best friend, offering a calm and grounded presence in contrast to Maggie’s fiery personality. Despite not being as close as Maggie and Rosie, Rebecca is a trusted member of their group, always willing to lend an ear or provide support during tough times. She often joins Maggie, Rosie, and Chrissy for their outings, like the memorable Christmas drink that becomes a turning point in their lives.</p>
|
||||
|
||||
<p>Rebecca is also the younger sister of Sophie, who couldn’t be more different in temperament. While Sophie shares their father’s sharp and often sinister demeanor, Rebecca takes after her mother—kind, warm, and down-to-earth. These traits make her approachable and likable, a stark contrast to Sophie’s hostility, which is directed at Maggie due to a mistaken identity.</p>
|
||||
|
||||
<p>Rebecca’s family mansion, a grand estate owned by her wealthy but secretive father, plays a pivotal role in the unfolding mystery. It’s revealed that Beth and Maggie’s mother, Annie, once worked in this mansion, connecting Rebecca’s family to the twins' past and the dark truths they must uncover. Although Rebecca is largely unaware of her father’s clandestine activities, her familial ties place her at the center of Maggie and Beth’s investigation.</p>
|
||||
|
||||
<p>Rebecca’s kind and loyal nature makes her an essential part of Maggie’s life, while her connection to the story’s antagonist adds layers of complexity to her character. She bridges the gap between Maggie’s personal friendships and the overarching mystery, making her role both personal and pivotal.</p>
|
||||
|
||||
<!-- Physical Description -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Physical Description</h2>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item"><strong>Hair:</strong> Dark blonde, with soft waves that complement her warm demeanor.</li>
|
||||
<li class="list-group-item"><strong>Eyes:</strong> Pale blue, expressive and filled with kindness.</li>
|
||||
<li class="list-group-item"><strong>Height:</strong> Slightly taller than average, with a graceful posture.</li>
|
||||
<li class="list-group-item"><strong>Style:</strong> Prefers elegant but understated outfits, often in soft tones that reflect her calm personality.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Personality Traits -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Personality Traits</h2>
|
||||
<div class="row d-flex">
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Kind</h5>
|
||||
<p class="card-text">Rebecca's compassion makes her an anchor for her friends, especially Maggie.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Loyal</h5>
|
||||
<p class="card-text">Her unwavering support for Maggie and their group defines her as a steadfast friend.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Grounded</h5>
|
||||
<p class="card-text">She brings a calming presence to the group, balancing out the more intense personalities around her.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Interests and Hobbies -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Interests and Hobbies</h2>
|
||||
<ul>
|
||||
<li>Enjoys reading classic literature, often recommending books to Maggie and Rosie.</li>
|
||||
<li>Loves gardening, spending weekends tending to the grounds of her family’s mansion.</li>
|
||||
<li>Plays the piano and occasionally performs at small gatherings.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Key Themes -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Key Themes</h2>
|
||||
<ul>
|
||||
<li>The contrast between her kind nature and her family’s darker secrets.</li>
|
||||
<li>Her role as a bridge between Maggie’s personal and investigative journeys.</li>
|
||||
<li>How loyalty and kindness can coexist with the burden of family ties.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Discover Rebecca Jones, Maggie Grant's second-best friend in The Alpha Flame. Learn about her kind-hearted nature, unshakeable loyalty, and her subtle influence in this captivating novel.">
|
||||
|
||||
<meta name="keywords" content="Rebecca Jones, The Alpha Flame, Catherine Lynwood, book characters, kind-hearted, loyal friend, supporting character, captivating novel themes">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebPage",
|
||||
"name": "Rebecca Jones - The Alpha Flame",
|
||||
"description": "Discover Rebecca Jones, Maggie Grant's second-best friend in The Alpha Flame. Learn about her kind-hearted nature, unshakeable loyalty, and her subtle influence in this captivating novel.",
|
||||
"url": "https://www.catherinelynwood.com/the-alpha-flame/characters/rebecca-jones",
|
||||
"author": {
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com"
|
||||
},
|
||||
"about": {
|
||||
"@@type": "CreativeWork",
|
||||
"name": "The Alpha Flame",
|
||||
"author": "Catherine Lynwood"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
68
CatherineLynwood/Views/TheAlphaFlame/Rick.cshtml
Normal file
@ -0,0 +1,68 @@
|
||||
@{
|
||||
ViewData["Title"] = "Rick";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">Meet The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Characters">Meet The Characters</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Rick</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Rick</h1>
|
||||
<p>
|
||||
<em>The Alpha Flame:</em> Definite Bad Guy
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="float-sm-end w-sm-50 p-3">
|
||||
<responsive-image src="rick-2.png" alt="Rick" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="30"></responsive-image>
|
||||
</div>
|
||||
<h2>Rick</h2>
|
||||
<p>I've quite literally had nightmares about this guy, and that is where the idea of him came from. The character of Rick is as mean and evil as he looks. To help inspire me in creating such a person I needed an image that shouted bad guy from the rooftops.</p>
|
||||
<h2 class="mb-3">Overview</h2>
|
||||
<p>Rick was once a nice guy. He's got obvious rough but rugged looks that some might find attractive, but in recent years he's fallen into bad ways.</p>
|
||||
<p>Rick is a pivotal antagonist in the story, embodying cruelty and manipulation. After Beth's mother passes away, Rick takes advantage of her vulnerability, befriending her under false pretenses. His true intentions are revealed as he coerces Beth into a dark lifestyle, exploiting her for his own gain.</p>
|
||||
<p>Rick’s role goes beyond just being a menace to Beth. His actions suggest a connection to a larger, sinister operation, possibly tied to the money and documents their mother, Annie, left behind. This gives him a more dangerous edge, as his motives seem to revolve not just around controlling Beth but also uncovering what she might know about Annie's secrets.</p>
|
||||
<p>The turning point comes when Rick confronts Maggie and Beth, thinking he has the upper hand. However, the girls work together, using their combined strength and cunning to defeat him. Left incapacitated, Rick is ultimately handed over to the authorities, but his involvement in the girls’ lives leaves a lasting scar.</p>
|
||||
<p>Rick is a character who represents the challenges and darkness the twins must face and overcome. His manipulative tactics and connection to a larger mystery make him a formidable and unforgettable adversary.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Explore Rick, the menacing antagonist in The Alpha Flame. Learn about his dark past, manipulative nature, and the challenges he poses to Maggie and Beth in this thrilling novel.">
|
||||
|
||||
<meta name="keywords" content="Rick, The Alpha Flame, Catherine Lynwood, book characters, antagonist, manipulative, menacing villain, thrilling novel themes">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebPage",
|
||||
"name": "Rick - The Alpha Flame",
|
||||
"description": "Explore Rick, the menacing antagonist in The Alpha Flame. Learn about his dark past, manipulative nature, and the challenges he poses to Maggie and Beth in this thrilling novel.",
|
||||
"url": "https://www.catherinelynwood.com/the-alpha-flame/characters/rick",
|
||||
"author": {
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com"
|
||||
},
|
||||
"about": {
|
||||
"@@type": "CreativeWork",
|
||||
"name": "The Alpha Flame",
|
||||
"author": "Catherine Lynwood"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
115
CatherineLynwood/Views/TheAlphaFlame/Rob.cshtml
Normal file
@ -0,0 +1,115 @@
|
||||
@{
|
||||
ViewData["Title"] = "Rob Jackson";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">Meet The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Characters">Meet The Characters</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Rob Jackson</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Overview Section -->
|
||||
<section class="mb-5">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Rob Jackson</h1>
|
||||
<p>
|
||||
<em>The Alpha Flame:</em> The Technical Mind
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="float-md-end w-sm-50 p-3">
|
||||
<responsive-image src="rob-jackson.png" alt="Rob Jackson" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="30"></responsive-image>
|
||||
</div>
|
||||
<h2>Rob Jackson</h2>
|
||||
<p>Rob is exactly the sort of guy you'd want your daughter to bring home with her.</p>
|
||||
<h2 class="mb-3">Overview</h2>
|
||||
<p>Rob Jackson is quiet, intelligent, and a bit of a geek, Rob is the man who sees the world differently—a quality that draws Maggie to him. Despite his awkwardness with women and a lack of confidence in himself, Rob possesses a kind heart and a sharp mind, making him an invaluable ally in Maggie's life and their shared adventures.</p>
|
||||
|
||||
<p>Rob is a talented photographer who started out in fashion but soon grew disillusioned with the industry. He often refers to the models he worked with as "clones," women who lacked originality or substance. This world-weary perspective on beauty makes him deeply appreciative of Maggie's fiery personality and her unflinching authenticity. Though their relationship starts on uneven footing, with Maggie sending him away during their first meeting, it’s Maggie who eventually asks him out, much to the amazement of his friends.</p>
|
||||
|
||||
<p>Rob loves his VW Golf GTi. It's his pride and joy, but he's always being bested by Maggie's TR6, something that needles him constantly.</p>
|
||||
|
||||
<p>Rob's love of gadgets and technical skills add a unique layer to the group’s dynamic. Whether he's tinkering with electronics, tapping phones, or counting pulses to track dialed numbers, Rob’s ingenuity often saves the day. His voyeuristic tendencies, which emerge through his photography and audio snooping, are both a strength and a quirk, making him an unconventional hero in Maggie's story.</p>
|
||||
|
||||
<!-- Physical Description -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Physical Description</h2>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item"><strong>Hair:</strong> Brown and slightly unkempt, with a style that reflects his no-nonsense attitude.</li>
|
||||
<li class="list-group-item"><strong>Eyes:</strong> Green, observant, and always calculating.</li>
|
||||
<li class="list-group-item"><strong>Height:</strong> Average, with a lean build.</li>
|
||||
<li class="list-group-item"><strong>Style:</strong> Casual and practical, often wearing jeans and a t-shirt with a utility jacket.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Personality Traits -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Personality Traits</h2>
|
||||
<div class="row d-flex">
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Intelligent</h5>
|
||||
<p class="card-text">Always thinking a few steps ahead, especially when it comes to technology.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Voyeristic Tendencies</h5>
|
||||
<p class="card-text">Rob loves to watch people, he's a keen observer of life and every aspect of it, some darker than others.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Resourceful</h5>
|
||||
<p class="card-text">Finds creative solutions to problems, often with gadgets and improvisation.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Discover Rob Jackson, the talented photographer and Maggie Grant's partner in The Alpha Flame. Learn about his technical skills, quiet determination, and his evolving role in this captivating novel.">
|
||||
|
||||
<meta name="keywords" content="Rob Jackson, The Alpha Flame, Catherine Lynwood, book characters, talented photographer, technical skills, partner, strong male lead, captivating novel themes">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebPage",
|
||||
"name": "Rob Jackson - The Alpha Flame",
|
||||
"description": "Discover Rob Jackson, the talented photographer and Maggie Grant's partner in The Alpha Flame. Learn about his technical skills, quiet determination, and his evolving role in this captivating novel.",
|
||||
"url": "https://www.catherinelynwood.com/the-alpha-flame/characters/rob-jackson",
|
||||
"author": {
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com"
|
||||
},
|
||||
"about": {
|
||||
"@@type": "CreativeWork",
|
||||
"name": "The Alpha Flame",
|
||||
"author": "Catherine Lynwood"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
148
CatherineLynwood/Views/TheAlphaFlame/Rosie.cshtml
Normal file
@ -0,0 +1,148 @@
|
||||
@{
|
||||
ViewData["Title"] = "Rosie MacDonald";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">Meet The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Characters">Meet The Characters</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Rosie MacDonald</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Rosie MacDonald</h1>
|
||||
<p>
|
||||
<em>The Alpha Flame:</em> Best friend of Maggie
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="float-sm-start w-sm-50 p-3">
|
||||
<responsive-image src="rosie-macdonald-2.png" alt="Rosie" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="40"></responsive-image>
|
||||
</div>
|
||||
<h2>Rosie MacDonald</h2>
|
||||
<p>Everyone needs a Rosie in their life. Rosie's character is one of sweet overwhelming loyalty.</p>
|
||||
<h2 class="mb-3">Overview</h2>
|
||||
<p>Rosie Macdonald is Maggie's best friend and unwavering ally. The two have been inseparable since their first days at infant school, forging a bond that’s more like sisterhood than friendship. Rosie is the quintessential girl next door—cute, bubbly, and effortlessly charming, with her blonde hair and delicate features. But behind her sweet appearance lies a fierce loyalty: she would die for Maggie, and Maggie loves her just as deeply.</p>
|
||||
<p>Rosie’s endearing personality makes her universally adored, though her popularity with the boys often stems from her curvy figure, particularly her ample chest—something that frequently embarrasses her, despite the attention. She’s the kind of person who wears her heart on her sleeve, always kind and quick to defend those she cares about.</p>
|
||||
<p>However, Rosie has a playful, uninhibited side that emerges with alcohol—especially vodka. Four drinks are her limit; any more, and her inhibitions vanish completely, leading to plenty of lighthearted antics (and occasionally the need for Maggie to step in).</p>
|
||||
<p>One night, her trusting nature nearly gets her into serious trouble when a man and their so-called "friend" Chrissy conspire to spike her drink. But Maggie and Rebecca discover the plot just in time. In a dramatic confrontation, Maggie uses her karate skills to take the man down, saving Rosie from what could have been a terrible fate. From that moment on, Rosie’s gratitude and devotion to Maggie only grow stronger.</p>
|
||||
<p>Rosie’s loyalty, innocence, and open-hearted love for Maggie make her a vital part of the group. She brings light and warmth wherever she goes, and her presence is a reminder that true friendship is as powerful as family.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!-- Physical Description -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Physical Description</h2>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item"><strong>Hair:</strong> Blonde, often styled in loose waves that frame her face.</li>
|
||||
<li class="list-group-item"><strong>Eyes:</strong> Soft blue, exuding warmth and sincerity.</li>
|
||||
<li class="list-group-item"><strong>Height:</strong> Slightly above average, with a curvy figure that she carries with grace.</li>
|
||||
<li class="list-group-item"><strong>Style:</strong> Prefers casual, comfortable clothing with a feminine touch, often opting for floral patterns and soft colors.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Personality Traits -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Personality Traits</h2>
|
||||
<div class="row d-flex">
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Loyal</h5>
|
||||
<p class="card-text">Stands by her friends through thick and thin, especially Maggie.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Bubbly</h5>
|
||||
<p class="card-text">Brings energy and positivity to any situation.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Trusting</h5>
|
||||
<p class="card-text">Sees the good in people, sometimes to a fault.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- Interests and Hobbies -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Interests and Hobbies</h2>
|
||||
<ul class="list-unstyled">
|
||||
<li class="mb-3">
|
||||
<i class="fad fa-oven"></i> <strong>Baking:</strong> Loves to bake treats for her friends and family.
|
||||
</li>
|
||||
<li class="mb-3">
|
||||
<i class="fad fa-music-alt"></i> <strong>Dancing:</strong> Enjoys letting loose on the dance floor, especially after a drink or two.
|
||||
</li>
|
||||
<li>
|
||||
<i class="fad fa-books"></i> <strong>Romantic Novels:</strong> An avid reader of love stories and often daydreams about her own.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Quotes -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Quotes from Maggie</h2>
|
||||
<blockquote class="blockquote">
|
||||
<p>"Life's too short not to have fun!"</p>
|
||||
</blockquote>
|
||||
<blockquote class="blockquote">
|
||||
<p>"I trust people until they give me a reason not to."</p>
|
||||
</blockquote>
|
||||
</section>
|
||||
|
||||
<!-- Did You Know -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Did You Know?</h2>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">Rosie has a collection of vintage aprons she wears while baking.</li>
|
||||
<li class="list-group-item">Despite her outgoing nature, she has a fear of public speaking.</li>
|
||||
<li class="list-group-item">She once auditioned for a local theater production on a dare from Maggie.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Meet Rosie MacDonald, Maggie Grant's best friend in The Alpha Flame. Discover her unwavering loyalty, quick wit, and role as a confidante and ally in this gripping novel.">
|
||||
|
||||
<meta name="keywords" content="Rosie MacDonald, The Alpha Flame, Catherine Lynwood, book characters, loyal friend, quick wit, strong female bond, captivating novel themes">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebPage",
|
||||
"name": "Rosie MacDonald - The Alpha Flame",
|
||||
"description": "Meet Rosie MacDonald, Maggie Grant's best friend in The Alpha Flame. Discover her unwavering loyalty, quick wit, and role as a confidante and ally in this gripping novel.",
|
||||
"url": "https://www.catherinelynwood.com/the-alpha-flame/characters/rosie-macdonald",
|
||||
"author": {
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com"
|
||||
},
|
||||
"about": {
|
||||
"@@type": "CreativeWork",
|
||||
"name": "The Alpha Flame",
|
||||
"author": "Catherine Lynwood"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
220
CatherineLynwood/Views/TheAlphaFlame/SlideShowTemplate.cshtml
Normal file
@ -0,0 +1,220 @@
|
||||
@model CatherineLynwood.Models.Blog
|
||||
@using System.Text.RegularExpressions
|
||||
|
||||
@{
|
||||
ViewData["Title"] = $"{Model.Title}";
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<partial name="_Breadcrumb" model="Model" />
|
||||
</div>
|
||||
</div>
|
||||
<article>
|
||||
@if (Model.ShowThanks)
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-success alert-dismissible fade show">
|
||||
<strong class="alert-heading">Success!</strong><button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button><br />
|
||||
<p>
|
||||
Your comments have been submitted and are awaiting approval. Thank you for your feedback.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<header>
|
||||
<h1 class="d-inline-block">@Model.Title</h1> <h2 class="h1 d-inline-block">@Model.SubTitle</h2>
|
||||
<p>Posted on @Model.PublishDate.ToString("MMMM d, yyyy") by Catherine Lynwood</p>
|
||||
</header>
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.AudioTranscriptUrl))
|
||||
{
|
||||
<div class="row justify-content-center p-3">
|
||||
<div class="col-auto">
|
||||
<audio controls>
|
||||
<source src="/audio/@Model.AudioTranscriptUrl" type="audio/mpeg">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-12">
|
||||
@if (Model.BlogImages.Count > 0)
|
||||
{
|
||||
<div class="float-md-start w-md-50">
|
||||
<div id="carouselExampleCaptions" class="carousel slide px-3 pb-3" data-bs-ride="false">
|
||||
<div class="carousel-indicators">
|
||||
@{
|
||||
int i = 0;
|
||||
|
||||
foreach (var image in Model.BlogImages)
|
||||
{
|
||||
if (image == Model.BlogImages.First())
|
||||
{
|
||||
<button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="@i" class="active" aria-current="true" aria-label="Slide @(i + 1)"></button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide-to="@i" aria-label="Slide @(i + 1)"></button>
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
</div>
|
||||
<div class="carousel-inner">
|
||||
@foreach (var image in Model.BlogImages)
|
||||
{
|
||||
if (image == Model.BlogImages.First())
|
||||
{
|
||||
<div class="carousel-item active">
|
||||
<responsive-image src="@image.ImageUrl" alt="@image.ImageCaption" class="d-block w-100 rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="50"></responsive-image>
|
||||
<div class="carousel-caption d-none d-md-block">
|
||||
<p class="h5">@image.ImageCaption</p>
|
||||
<p>@image.ImageText</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="carousel-item">
|
||||
<responsive-image src="@image.ImageUrl" alt="@image.ImageCaption" class="d-block w-100 rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="50"></responsive-image>
|
||||
<div class="carousel-caption d-none d-md-block">
|
||||
<h5>@image.ImageCaption</h5>
|
||||
<p>@image.ImageText</p>
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
}
|
||||
|
||||
</div>
|
||||
<button class="carousel-control-prev" type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Previous</span>
|
||||
</button>
|
||||
<button class="carousel-control-next" type="button" data-bs-target="#carouselExampleCaptions" data-bs-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="visually-hidden">Next</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Model.ContentTop))
|
||||
{
|
||||
@Html.Raw(Model.ContentTop)
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Model.AudioTeaserUrl))
|
||||
{
|
||||
<div class="row justify-content-center bg-white text-dark rounded-3 p-3">
|
||||
<div class="col-auto">
|
||||
<audio controls>
|
||||
<source src="/audio/@Model.AudioTeaserUrl" type="audio/mpeg">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
</div>
|
||||
@if (!string.IsNullOrWhiteSpace(Model.AudioTeaserText))
|
||||
{
|
||||
<div class="col-12" style="max-height: 120px; overflow-y: auto;">
|
||||
@Html.Raw(Model.AudioTeaserText)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (!string.IsNullOrWhiteSpace(Model.ContentBottom))
|
||||
{
|
||||
@Html.Raw(Model.ContentBottom)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
@await Component.InvokeAsync("BlogCommentComponent", new { BlogID = Model.BlogID })
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Explore the blog of Catherine Lynwood, author of *The Alpha Flame*, for updates, insights, and behind-the-scenes stories. Follow Catherine’s journey as a writer, discover inspiration for her novels, and delve into discussions on women’s fiction, mystery, and family secrets.">
|
||||
<meta name="keywords" content="Catherine Lynwood blog, The Alpha Flame updates, women’s fiction blog, author Catherine Lynwood insights, mystery novel blog, family secrets stories, strong female protagonists, 1980s fiction blog, book updates and stories, author’s journey blog, Catherine Lynwood writing process">
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
@functions {
|
||||
public static string StripHtml(string input)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(input) ? string.Empty : Regex.Replace(input, "<.*?>", string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
@{
|
||||
string blogUrl = $"https://www.catherinelynwood.com/{Model.BlogUrl}";
|
||||
string imageUrl = $"https://www.catherinelynwood.com/images/webp/{Model.DefaultWebpImage}";
|
||||
string description = $"Listen to my new blog entry: {Model.Title} by Catherine Lynwood";
|
||||
|
||||
var contentTopPlainText = StripHtml(Model.ContentTop);
|
||||
var contentBottomPlainText = StripHtml(Model.ContentBottom);
|
||||
|
||||
var blogPostJsonLd = $@"
|
||||
{{
|
||||
""@context"": ""https://schema.org"",
|
||||
""@type"": ""BlogPosting"",
|
||||
""headline"": ""{Model.Title}"",
|
||||
""datePublished"": ""{Model.PublishDate:yyyy-MM-dd}"",
|
||||
""author"": {{
|
||||
""@type"": ""Person"",
|
||||
""name"": ""Catherine Lynwood""
|
||||
}},
|
||||
""description"": ""{Model.IndexText}"",
|
||||
""articleBody"": ""{contentTopPlainText} {contentBottomPlainText}"",
|
||||
""url"": ""https://www.catherinelynwood.com/the-alpah-flame/blog/{Model.BlogUrl}"",
|
||||
""image"": {{
|
||||
""@type"": ""ImageObject"",
|
||||
""url"": ""https://www.catherinelynwood.com/images/webp/{Model.DefaultWebpImage}"",
|
||||
""description"": ""{Model.ImageDescription}""
|
||||
}},
|
||||
""interactionStatistic"": {{
|
||||
""@type"": ""InteractionCounter"",
|
||||
""interactionType"": {{
|
||||
""@type"": ""http://schema.org/LikeAction""
|
||||
}},
|
||||
""userInteractionCount"": {Model.Likes}
|
||||
}}
|
||||
}}";
|
||||
}
|
||||
|
||||
<meta property="og:title" content="The Alpha Flame by Catherine Lynwood" />
|
||||
<meta property="og:description" content="@Model.IndexText" />
|
||||
<meta property="og:type" content="article" />
|
||||
<meta property="og:url" content="@blogUrl" />
|
||||
<meta property="og:image" content="@imageUrl" />
|
||||
<meta property="og:image:alt" content="@Model.ImageAlt" />
|
||||
<meta property="og:site_name" content="Catherine Lynwood - The Alpha Flame" />
|
||||
<meta property="article:author" content="https://www.catherinelynwood.com" />
|
||||
<meta property="article:published_time" content="2024-11-19" />
|
||||
<meta property="article:modified_time" content="2024-11-19" />
|
||||
|
||||
<meta name="twitter:card" content="player">
|
||||
<meta name="twitter:title" content="@Model.Title" />
|
||||
<meta name="twitter:description" content="@description">
|
||||
<meta name="twitter:site" content="@@CathLynwood" />
|
||||
<meta name="twitter:creator" content="@@CathLynwood" />
|
||||
<meta name="twitter:image" content="@imageUrl" />
|
||||
<meta name="twitter:image:alt" content="@Model.ImageAlt" />
|
||||
<meta name="twitter:player" content="@blogUrl">
|
||||
<meta name="twitter:player:width" content="480">
|
||||
<meta name="twitter:player:height" content="80">
|
||||
|
||||
<script type="application/ld+json">
|
||||
@Html.Raw(blogPostJsonLd)
|
||||
</script>
|
||||
|
||||
}
|
||||
|
||||
66
CatherineLynwood/Views/TheAlphaFlame/Sophie.cshtml
Normal file
@ -0,0 +1,66 @@
|
||||
@{
|
||||
ViewData["Title"] = "Sophie Jones";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">Meet The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Characters">Meet The Characters</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Sophie Jones</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<section class="hero-section mb-5">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Sophie Jones</h1>
|
||||
<p>
|
||||
<em>The Alpha Flame:</em> Protagonist to Maggie
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<responsive-image src="sophie-jones-2.png" alt="Sophie Jones" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="30"></responsive-image>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h2>Sophie Jones</h2>
|
||||
<p>I think most people knew a Sophie Jones when they were growing up. The girl who has everything and loves to do nothing more than rub it in everyone's face. Sophie is a number one bitch. She's nasty and spiteful for no reason at all, or at least none that you can see, but maybe there is a reason?</p>
|
||||
<h2>Overview</h2>
|
||||
<p>Sophie is a total princess. First daughter of a multi-millionaire father her worships the ground she walks on. He showers her with all sorts of material goods from expensive clothes and jewelery, to top of the range super cars.</p>
|
||||
<p>Sophie is defintiely a chip off the old block, and at 21 years old is firmly following in her daddy's footsteps.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Uncover Sophie Jones, the antagonistic and privileged figure in The Alpha Flame. Learn about her cold demeanor, strained relationships, and her role in adding tension and intrigue to this gripping novel.">
|
||||
|
||||
<meta name="keywords" content="Sophie Jones, The Alpha Flame, Catherine Lynwood, book characters, antagonist, privileged, cold demeanor, strained relationships, gripping novel themes">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebPage",
|
||||
"name": "Sophie Jones - The Alpha Flame",
|
||||
"description": "Uncover Sophie Jones, the antagonistic and privileged figure in The Alpha Flame. Learn about her cold demeanor, strained relationships, and her role in adding tension and intrigue to this gripping novel.",
|
||||
"url": "https://www.catherinelynwood.com/the-alpha-flame/characters/sophie-jones",
|
||||
"author": {
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com"
|
||||
},
|
||||
"about": {
|
||||
"@@type": "CreativeWork",
|
||||
"name": "The Alpha Flame",
|
||||
"author": "Catherine Lynwood"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
134
CatherineLynwood/Views/TheAlphaFlame/Zoe.cshtml
Normal file
@ -0,0 +1,134 @@
|
||||
@{
|
||||
ViewData["Title"] = "Zoe Davies";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Index">Meet The Alpha Flame</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Characters">Meet The Characters</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Zoe Davies</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Zoe Davies</h1>
|
||||
<p>
|
||||
<em>The Alpha Flame:</em> Close friend to Maggie
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<div class="col-12">
|
||||
<div class="float-sm-end w-sm-50 p-3">
|
||||
<responsive-image src="zoe-davies.png" alt="Zoe Davies" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" display-width-percentage="30"></responsive-image>
|
||||
</div>
|
||||
<h2>Zoe Davies</h2>
|
||||
<p>Zoe is a strong but partially defeated character. She's had a rought time and needs something to go right in her life. I guess we've all had that happen to us.</p>
|
||||
<h2 class="mb-3">Overview</h2>
|
||||
<p>Zoe Davies is a woman seeking peace and normalcy in her life after leaving a turbulent career in the police force. Now a driving examiner in Redditch, Zoe finds herself caught between her love of working with people and the emotional strain of failing drivers who aren’t ready for the road. Despite her calm exterior, Zoe hides scars from a past shaped by prejudice and relentless discrimination.</p>
|
||||
|
||||
<p>Just a year ago, Zoe was a dedicated policewoman with five years of service under her belt. As the only woman on her shift, she often handled sensitive cases requiring empathy and tact, roles her male colleagues weren’t equipped to take on. However, her life took a sharp turn when her sexuality became known after she was seen leaving a nightclub with her then-partner, Sally. What started as snide comments and jokes quickly escalated into open hostility. The relentless abuse—from being called "The Dyke" to being excluded from key operations—drove Zoe to leave a career she had once loved.</p>
|
||||
|
||||
<p>Now, as a driving examiner, Zoe has started to rebuild her life. She has no plans to disclose her personal life to her new colleagues, choosing instead to maintain a quiet and private existence. Her only companion at home is George, her beloved cat, who provides comfort and companionship in her small flat. Though she misses the thrill and camaraderie of her old life, Zoe is determined to find peace and happiness on her own terms.</p>
|
||||
|
||||
<p>Despite her efforts to move on, Zoe can’t help but reflect on her past, especially the moments of injustice that forced her to leave the police force. She’s strong, resilient, and introspective, though loneliness often creeps in. But even amid her quiet New Year’s Eve with George and a rented copy of <em>Monty Python’s Life of Brian</em>, Zoe can’t stop thinking about the young driver she passed on Christmas Eve—a woman who, with her lightning-fast reactions, saved a child’s life. Zoe recalls her admiration and attraction to the woman, a fleeting spark that reminds her that, even in her quiet life, unexpected connections can still light a fire within her.</p>
|
||||
|
||||
<!-- Physical Description -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Physical Description</h2>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item"><strong>Hair:</strong> Dark brown, typically tied back or left loose in a practical style.</li>
|
||||
<li class="list-group-item"><strong>Eyes:</strong> Hazel, often reflecting her thoughtful and introspective nature.</li>
|
||||
<li class="list-group-item"><strong>Height:</strong> Average, with a lean and athletic build from her years on the police force.</li>
|
||||
<li class="list-group-item"><strong>Style:</strong> Casual and understated, favoring comfortable clothes that don’t draw attention.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Personality Traits -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Personality Traits</h2>
|
||||
<div class="row d-flex">
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Resilient</h5>
|
||||
<p class="card-text">Zoe has faced significant adversity and emerged stronger, determined to rebuild her life on her own terms.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Empathetic</h5>
|
||||
<p class="card-text">Her caring nature allows her to connect with people on a deeper level, often providing support when others can’t.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 d-flex">
|
||||
<div class="card border-light mb-3 flex-grow-1">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Private</h5>
|
||||
<p class="card-text">After facing discrimination, Zoe values her privacy and chooses to keep her personal life to herself.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Interests and Hobbies -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Interests and Hobbies</h2>
|
||||
<ul>
|
||||
<li><i class="fad fa-cat"></i> <strong>Cats:</strong> Spending time with her cat, George, who is her closest companion and confidant.</li>
|
||||
<li><i class="fad fa-theater-masks"></i> <strong>Comedies:</strong> Watching comedies, such as <em>Monty Python’s Life of Brian</em>, to lighten her mood.</li>
|
||||
<li><i class="fad fa-pie"></i> <strong>Cooking:</strong> Keen cook and has hundreds of cookery books, which she finds calming and rewarding.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<!-- Key Themes -->
|
||||
<section class="mb-5">
|
||||
<h2 class="mb-3">Key Themes</h2>
|
||||
<ul>
|
||||
<li>Rebuilding a life after experiencing discrimination and loss.</li>
|
||||
<li>The importance of privacy and self-preservation.</li>
|
||||
<li>Finding comfort in small moments, such as time with George or a relaxing bath.</li>
|
||||
<li>Strength through quiet resilience and introspection.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Meta {
|
||||
<meta name="description" content="Meet Zoe Davies, the ex-policewoman turned driving examiner in The Alpha Flame. Discover her sharp instincts, calm demeanor, and pivotal role as a mentor and ally in this gripping novel.">
|
||||
|
||||
<meta name="keywords" content="Zoe Davies, The Alpha Flame, Catherine Lynwood, book characters, ex-policewoman, driving examiner, mentor, sharp instincts, captivating novel themes">
|
||||
|
||||
<meta name="author" content="Catherine Lynwood">
|
||||
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@@context": "https://schema.org",
|
||||
"@@type": "WebPage",
|
||||
"name": "Zoe Davies - The Alpha Flame",
|
||||
"description": "Meet Zoe Davies, the ex-policewoman turned driving examiner in The Alpha Flame. Discover her sharp instincts, calm demeanor, and pivotal role as a mentor and ally in this gripping novel.",
|
||||
"url": "https://www.catherinelynwood.com/the-alpha-flame/characters/zoe-davies",
|
||||
"author": {
|
||||
"@@type": "Person",
|
||||
"name": "Catherine Lynwood",
|
||||
"url": "https://www.catherinelynwood.com"
|
||||
},
|
||||
"about": {
|
||||
"@@type": "CreativeWork",
|
||||
"name": "The Alpha Flame",
|
||||
"author": "Catherine Lynwood"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
}
|
||||
13
CatherineLynwood/Views/TheAlphaFlame/_Breadcrumb.cshtml
Normal file
@ -0,0 +1,13 @@
|
||||
@model CatherineLynwood.Models.Blog
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a asp-controller="Home" asp-action="Index">Home</a></li>
|
||||
<li class="breadcrumb-item"><a asp-controller="TheAlphaFlame" asp-action="Blog">The Alpha Flame Blog</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">@Model.Title @Model.SubTitle</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
4
CatherineLynwood/Views/_ViewImports.cshtml
Normal file
@ -0,0 +1,4 @@
|
||||
@using CatherineLynwood
|
||||
@using CatherineLynwood.Models
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@addTagHelper *, CatherineLynwood
|
||||
3
CatherineLynwood/Views/_ViewStart.cshtml
Normal file
@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
||||
8
CatherineLynwood/appsettings.Development.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
15
CatherineLynwood/appsettings.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=localhost;Database=CatherineLynwood;Trusted_Connection=True;MultipleActiveResultSets=true;Encrypt=false;"
|
||||
},
|
||||
"Email": {
|
||||
"APIKey": "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"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
32
CatherineLynwood/bundleconfig.json
Normal file
@ -0,0 +1,32 @@
|
||||
[
|
||||
{
|
||||
"outputFileName": "wwwroot/lib/bootstrap/dist/css/bootstrap.min.css",
|
||||
"inputFiles": [
|
||||
"wwwroot/lib/bootstrap/dist/css/bootstrap.css"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css",
|
||||
"inputFiles": [
|
||||
"wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css",
|
||||
"inputFiles": [
|
||||
"wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css",
|
||||
"inputFiles": [
|
||||
"wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outputFileName": "wwwroot/css/site.min.css",
|
||||
"inputFiles": [
|
||||
"wwwroot/css/site.css"
|
||||
]
|
||||
}
|
||||
]
|
||||
40
CatherineLynwood/package-lock.json
generated
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "CatherineLynwood",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
|
||||
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
|
||||
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/twbs"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/bootstrap"
|
||||
}
|
||||
],
|
||||
"peerDependencies": {
|
||||
"@popperjs/core": "^2.11.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
CatherineLynwood/package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.3"
|
||||
}
|
||||
}
|
||||
BIN
CatherineLynwood/wwwroot/android-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
CatherineLynwood/wwwroot/android-icon-192x192.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
CatherineLynwood/wwwroot/android-icon-36x36.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
CatherineLynwood/wwwroot/android-icon-48x48.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
CatherineLynwood/wwwroot/android-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
CatherineLynwood/wwwroot/android-icon-96x96.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
CatherineLynwood/wwwroot/apple-icon-114x114.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
CatherineLynwood/wwwroot/apple-icon-120x120.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
CatherineLynwood/wwwroot/apple-icon-144x144.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
CatherineLynwood/wwwroot/apple-icon-152x152.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
CatherineLynwood/wwwroot/apple-icon-180x180.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
CatherineLynwood/wwwroot/apple-icon-57x57.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
CatherineLynwood/wwwroot/apple-icon-60x60.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
CatherineLynwood/wwwroot/apple-icon-72x72.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
CatherineLynwood/wwwroot/apple-icon-76x76.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
CatherineLynwood/wwwroot/apple-icon-precomposed.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
CatherineLynwood/wwwroot/apple-icon.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
CatherineLynwood/wwwroot/audio/chapter-1-beth-excerpt.mp3
Normal file
BIN
CatherineLynwood/wwwroot/audio/chapter-2-maggie-excerpt.mp3
Normal file
BIN
CatherineLynwood/wwwroot/audio/podcast-1.mp3
Normal file
BIN
CatherineLynwood/wwwroot/audio/samantha-lynwood.mp3
Normal file
2
CatherineLynwood/wwwroot/browserconfig.xml
Normal file
@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#17C8EB</TileColor></tile></msapplication></browserconfig>
|
||||
12785
CatherineLynwood/wwwroot/css/all.css
Normal file
5
CatherineLynwood/wwwroot/css/all.min.css
vendored
Normal file
15
CatherineLynwood/wwwroot/css/brands.css
Normal file
@ -0,0 +1,15 @@
|
||||
/*!
|
||||
* Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Brands';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-brands-400.eot");
|
||||
src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg"); }
|
||||
|
||||
.fab {
|
||||
font-family: 'Font Awesome 5 Brands';
|
||||
font-weight: 400; }
|
||||
5
CatherineLynwood/wwwroot/css/brands.min.css
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/*!
|
||||
* Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
*/
|
||||
@font-face{font-family:"Font Awesome 5 Brands";font-style:normal;font-weight:400;font-display:block;src:url(../webfonts/fa-brands-400.eot);src:url(../webfonts/fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-brands-400.woff2) format("woff2"),url(../webfonts/fa-brands-400.woff) format("woff"),url(../webfonts/fa-brands-400.ttf) format("truetype"),url(../webfonts/fa-brands-400.svg#fontawesome) format("svg")}.fab{font-family:"Font Awesome 5 Brands";font-weight:400}
|
||||
5609
CatherineLynwood/wwwroot/css/duotone.css
Normal file
5
CatherineLynwood/wwwroot/css/duotone.min.css
vendored
Normal file
7135
CatherineLynwood/wwwroot/css/fontawesome.css
vendored
Normal file
5
CatherineLynwood/wwwroot/css/fontawesome.min.css
vendored
Normal file
15
CatherineLynwood/wwwroot/css/light.css
Normal file
@ -0,0 +1,15 @@
|
||||
/*!
|
||||
* Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-light-300.eot");
|
||||
src: url("../webfonts/fa-light-300.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-light-300.woff2") format("woff2"), url("../webfonts/fa-light-300.woff") format("woff"), url("../webfonts/fa-light-300.ttf") format("truetype"), url("../webfonts/fa-light-300.svg#fontawesome") format("svg"); }
|
||||
|
||||
.fal {
|
||||
font-family: 'Font Awesome 5 Pro';
|
||||
font-weight: 300; }
|
||||
5
CatherineLynwood/wwwroot/css/light.min.css
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/*!
|
||||
* Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
*/
|
||||
@font-face{font-family:"Font Awesome 5 Pro";font-style:normal;font-weight:300;font-display:block;src:url(../webfonts/fa-light-300.eot);src:url(../webfonts/fa-light-300.eot?#iefix) format("embedded-opentype"),url(../webfonts/fa-light-300.woff2) format("woff2"),url(../webfonts/fa-light-300.woff) format("woff"),url(../webfonts/fa-light-300.ttf) format("truetype"),url(../webfonts/fa-light-300.svg#fontawesome) format("svg")}.fal{font-family:"Font Awesome 5 Pro";font-weight:300}
|
||||
15
CatherineLynwood/wwwroot/css/regular.css
Normal file
@ -0,0 +1,15 @@
|
||||
/*!
|
||||
* Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com
|
||||
* License - https://fontawesome.com/license (Commercial License)
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Font Awesome 5 Pro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: block;
|
||||
src: url("../webfonts/fa-regular-400.eot");
|
||||
src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg"); }
|
||||
|
||||
.far {
|
||||
font-family: 'Font Awesome 5 Pro';
|
||||
font-weight: 400; }
|
||||