Save
@ -3,11 +3,14 @@ using CatherineLynwood.Models;
|
|||||||
using CatherineLynwood.Services;
|
using CatherineLynwood.Services;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Identity.Client;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
using SendGrid.Helpers.Mail;
|
using SendGrid.Helpers.Mail;
|
||||||
|
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace CatherineLynwood.Controllers
|
namespace CatherineLynwood.Controllers
|
||||||
{
|
{
|
||||||
[Route("the-alpha-flame")]
|
[Route("the-alpha-flame")]
|
||||||
@ -36,7 +39,6 @@ namespace CatherineLynwood.Controllers
|
|||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Route("blog")]
|
[Route("blog")]
|
||||||
public async Task<IActionResult> Blog(BlogFilter blogFilter)
|
public async Task<IActionResult> Blog(BlogFilter blogFilter)
|
||||||
{
|
{
|
||||||
@ -93,6 +95,9 @@ namespace CatherineLynwood.Controllers
|
|||||||
|
|
||||||
blog.ShowThanks = showThanks;
|
blog.ShowThanks = showThanks;
|
||||||
|
|
||||||
|
// Generate JSON-LD
|
||||||
|
blog.SchemaJsonLd = GenerateBlogSchemaJsonLd(blog);
|
||||||
|
|
||||||
if (blog.Template == "slideshow")
|
if (blog.Template == "slideshow")
|
||||||
{
|
{
|
||||||
return View("SlideShowTemplate", blog);
|
return View("SlideShowTemplate", blog);
|
||||||
@ -101,8 +106,6 @@ namespace CatherineLynwood.Controllers
|
|||||||
return View("DefaultTemplate", blog);
|
return View("DefaultTemplate", blog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[Route("characters")]
|
[Route("characters")]
|
||||||
public IActionResult Characters()
|
public IActionResult Characters()
|
||||||
{
|
{
|
||||||
@ -164,6 +167,12 @@ namespace CatherineLynwood.Controllers
|
|||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Route("giveaways")]
|
||||||
|
public IActionResult Giveaways()
|
||||||
|
{
|
||||||
|
return View();
|
||||||
|
}
|
||||||
|
|
||||||
[Route("characters/maggie-grant")]
|
[Route("characters/maggie-grant")]
|
||||||
public IActionResult Maggie()
|
public IActionResult Maggie()
|
||||||
{
|
{
|
||||||
@ -218,13 +227,74 @@ namespace CatherineLynwood.Controllers
|
|||||||
return View();
|
return View();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endregion Public Methods
|
#endregion Public Methods
|
||||||
|
|
||||||
#region Private Methods
|
#region Private Methods
|
||||||
|
|
||||||
|
private string GenerateBlogSchemaJsonLd(Blog blog)
|
||||||
|
{
|
||||||
|
// Strip HTML from content
|
||||||
|
string StripHtml(string input)
|
||||||
|
=> string.IsNullOrWhiteSpace(input) ? string.Empty : Regex.Replace(input, "<.*?>", string.Empty);
|
||||||
|
|
||||||
|
string contentTopPlainText = StripHtml(blog.ContentTop);
|
||||||
|
string contentBottomPlainText = StripHtml(blog.ContentBottom);
|
||||||
|
string blogUrl = $"https://www.catherinelynwood.com/{blog.BlogUrl}";
|
||||||
|
|
||||||
|
// Build the schema object
|
||||||
|
var schema = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["@context"] = "https://schema.org",
|
||||||
|
["@type"] = "BlogPosting",
|
||||||
|
["headline"] = blog.Title,
|
||||||
|
["datePublished"] = blog.PublishDate.ToString("yyyy-MM-dd"),
|
||||||
|
["author"] = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["@type"] = "Person",
|
||||||
|
["name"] = "Catherine Lynwood"
|
||||||
|
},
|
||||||
|
["description"] = blog.IndexText,
|
||||||
|
["articleBody"] = $"{contentTopPlainText} {contentBottomPlainText}",
|
||||||
|
["url"] = blogUrl,
|
||||||
|
["interactionStatistic"] = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["@type"] = "InteractionCounter",
|
||||||
|
["interactionType"] = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["@type"] = "http://schema.org/LikeAction"
|
||||||
|
},
|
||||||
|
["userInteractionCount"] = blog.Likes
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(blog.ImageUrl))
|
||||||
|
{
|
||||||
|
schema["image"] = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["@type"] = "ImageObject",
|
||||||
|
["url"] = $"https://www.catherinelynwood.com/images/webp/{blog.DefaultWebpImage}",
|
||||||
|
["description"] = blog.ImageDescription
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(blog.VideoUrl))
|
||||||
|
{
|
||||||
|
schema["video"] = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
["@type"] = "VideoObject",
|
||||||
|
["name"] = $"{blog.Title} – Video Teaser",
|
||||||
|
["description"] = $"Atmospheric teaser video for {blog.Title}.",
|
||||||
|
["thumbnailUrl"] = $"https://www.catherinelynwood.com/images/webp/{blog.DefaultWebpImage}",
|
||||||
|
["uploadDate"] = blog.PublishDate.ToString("yyyy-MM-dd"),
|
||||||
|
["contentUrl"] = blog.VideoUrl,
|
||||||
|
["embedUrl"] = blog.VideoUrl
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonConvert.SerializeObject(schema, Formatting.Indented);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsMobile(HttpRequest request)
|
private bool IsMobile(HttpRequest request)
|
||||||
{
|
{
|
||||||
string userAgent = request.Headers["User-Agent"].ToString().ToLower();
|
string userAgent = request.Headers["User-Agent"].ToString().ToLower();
|
||||||
|
|||||||
29
CatherineLynwood/Middleware/RedirectMiddleware.cs
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
using CatherineLynwood.Services;
|
||||||
|
|
||||||
|
namespace CatherineLynwood.Middleware
|
||||||
|
{
|
||||||
|
public class RedirectMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
public RedirectMiddleware(RequestDelegate next)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context, RedirectsStore store)
|
||||||
|
{
|
||||||
|
var path = context.Request.Path.Value ?? "";
|
||||||
|
|
||||||
|
if (store.TryGetRedirect(path, out var newLocation))
|
||||||
|
{
|
||||||
|
context.Response.StatusCode = StatusCodes.Status301MovedPermanently;
|
||||||
|
context.Response.Headers.Location = newLocation;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -50,6 +50,8 @@
|
|||||||
|
|
||||||
public DateTime PublishDate { get; set; }
|
public DateTime PublishDate { get; set; }
|
||||||
|
|
||||||
|
public string SchemaJsonLd { get; set; }
|
||||||
|
|
||||||
public bool ShowThanks { get; set; }
|
public bool ShowThanks { get; set; }
|
||||||
|
|
||||||
public string SubTitle { get; set; }
|
public string SubTitle { get; set; }
|
||||||
|
|||||||
@ -35,6 +35,13 @@ namespace CatherineLynwood
|
|||||||
options.Cookie.IsEssential = true;
|
options.Cookie.IsEssential = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add RedirectsStore as singleton
|
||||||
|
builder.Services.AddSingleton<RedirectsStore>(sp =>
|
||||||
|
{
|
||||||
|
var logger = sp.GetRequiredService<ILogger<RedirectsStore>>();
|
||||||
|
return new RedirectsStore("redirects.json", logger);
|
||||||
|
});
|
||||||
|
|
||||||
// ✅ Register the book access code service
|
// ✅ Register the book access code service
|
||||||
builder.Services.AddScoped<IAccessCodeService, AccessCodeService>();
|
builder.Services.AddScoped<IAccessCodeService, AccessCodeService>();
|
||||||
|
|
||||||
@ -89,6 +96,9 @@ namespace CatherineLynwood
|
|||||||
app.UseMiddleware<HoneypotLoggingMiddleware>();
|
app.UseMiddleware<HoneypotLoggingMiddleware>();
|
||||||
app.UseMiddleware<IpqsBlockMiddleware>();
|
app.UseMiddleware<IpqsBlockMiddleware>();
|
||||||
|
|
||||||
|
app.UseMiddleware<RedirectMiddleware>();
|
||||||
|
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
app.UseResponseCompression();
|
app.UseResponseCompression();
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
|||||||
86
CatherineLynwood/Services/RedirectsStore.cs
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace CatherineLynwood.Services
|
||||||
|
{
|
||||||
|
|
||||||
|
public class RedirectsStore : IDisposable
|
||||||
|
{
|
||||||
|
private readonly string _filePath;
|
||||||
|
private readonly ILogger<RedirectsStore> _logger;
|
||||||
|
private readonly FileSystemWatcher _watcher;
|
||||||
|
private readonly ConcurrentDictionary<string, string> _redirects = new();
|
||||||
|
|
||||||
|
public RedirectsStore(string filePath, ILogger<RedirectsStore> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
// Ensure absolute path
|
||||||
|
_filePath = Path.GetFullPath(filePath);
|
||||||
|
|
||||||
|
// Split into directory and filename
|
||||||
|
var directory = Path.GetDirectoryName(_filePath);
|
||||||
|
var filename = Path.GetFileName(_filePath);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(directory) || string.IsNullOrEmpty(filename))
|
||||||
|
{
|
||||||
|
throw new ArgumentException($"Invalid redirects.json path: {filePath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Watching redirects file in directory: {Directory}, file: {File}", directory, filename);
|
||||||
|
|
||||||
|
// Load initial data
|
||||||
|
LoadRedirects();
|
||||||
|
|
||||||
|
// Watch for changes
|
||||||
|
_watcher = new FileSystemWatcher(directory, filename)
|
||||||
|
{
|
||||||
|
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size
|
||||||
|
};
|
||||||
|
|
||||||
|
_watcher.Changed += (_, _) =>
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Redirects file changed, reloading...");
|
||||||
|
LoadRedirects();
|
||||||
|
};
|
||||||
|
|
||||||
|
_watcher.EnableRaisingEvents = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void LoadRedirects()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var text = File.ReadAllText(_filePath);
|
||||||
|
var data = JsonSerializer.Deserialize<Dictionary<string, string>>(text) ?? new();
|
||||||
|
|
||||||
|
_redirects.Clear();
|
||||||
|
foreach (var pair in data)
|
||||||
|
{
|
||||||
|
var key = pair.Key.TrimEnd('/').ToLowerInvariant();
|
||||||
|
var value = pair.Value;
|
||||||
|
_redirects[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("Loaded {Count} redirect rules", _redirects.Count);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Failed to load redirects from {File}", _filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetRedirect(string path, out string newPath)
|
||||||
|
{
|
||||||
|
var normalised = path.TrimEnd('/').ToLowerInvariant();
|
||||||
|
return _redirects.TryGetValue(normalised, out newPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_watcher.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
@{
|
@{
|
||||||
ViewData["Title"] = "The Alpha Flame: Discovery";
|
ViewData["Title"] = "The Alpha Flame: A Gritty 1980s Birmingham Crime Novel about Twin Sisters";
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<section id="book-cover">
|
<section id="book-cover">
|
||||||
<div class="card character-card" id="cover-card">
|
<div class="card character-card" id="cover-card">
|
||||||
<responsive-image src="the-alpha-flame-11.png" class="card-img-top" alt="The Alpha Flame Cover" display-width-percentage="50"></responsive-image>
|
<responsive-image src="the-alpha-flame-11.png" class="card-img-top" alt="The Alpha Flame book cover — gritty 1980s Birmingham crime novel about twin sisters uncovering secrets and surviving abuse" display-width-percentage="50"></responsive-image>
|
||||||
<div class="card-body border-top border-3 border-dark">
|
<div class="card-body border-top border-3 border-dark">
|
||||||
<h3 class="card-title">The Front Cover</h3>
|
<h3 class="card-title">The Front Cover</h3>
|
||||||
<p class="card-text">This is the final front cover of The Alpha Flame: Discovery. It features Maggie stood outside the derelict Rubery Hill Hospital.</p>
|
<p class="card-text">This is the final front cover of The Alpha Flame: Discovery. It features Maggie stood outside the derelict Rubery Hill Hospital.</p>
|
||||||
@ -34,12 +34,13 @@
|
|||||||
<section id="synopsis">
|
<section id="synopsis">
|
||||||
<div class="card character-card" id="synopsis-card">
|
<div class="card character-card" id="synopsis-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h1>The Alpha Flame: <span class="fw-light">Discovery</span></h1>
|
<h1>The Alpha Flame: <span class="fw-light">Discovery</span><br /><span class="h2">A Gritty 1980s Birmingham Crime Novel</span></h1>
|
||||||
|
<h2 class="h3">Survival, secrets, and sisters in 1980s Birmingham.</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body" id="synopsis-body">
|
<div class="card-body" id="synopsis-body">
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<responsive-image src="catherine-lynwood-16.png" class="img-fluid rounded-circle" alt="Catherine Lynwood" display-width-percentage="50"></responsive-image>
|
<responsive-image src="catherine-lynwood-16.png" class="img-fluid rounded-circle border border-2 border-dark" alt="Catherine Lynwood" display-width-percentage="100"></responsive-image>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
<!-- Audio Section -->
|
<!-- Audio Section -->
|
||||||
@ -85,7 +86,7 @@
|
|||||||
|
|
||||||
<!-- Synopsis Content -->
|
<!-- Synopsis Content -->
|
||||||
<h3 class="card-title">Synopsis</h3>
|
<h3 class="card-title">Synopsis</h3>
|
||||||
<p class="card-text">The Alpha Flame is an unflinching and deeply emotional story of resilience, love, and survival, set against the vibrant yet tumultuous backdrop of 1983. Through the intersecting lives of two heroines, it delves into the extremes of human suffering and the boundless power of the human spirit to endure, adapt, and rise.</p>
|
<p class="card-text">Set in 1983 Birmingham, The Alpha Flame: Discovery is a gritty crime novel following twin sisters Beth and Maggie as they uncover dark family secrets and fight to survive abuse in a harsh, realistic world. With unflinching honesty, it explores the bonds of family, the scars of the past, and the resilience needed to endure. This powerful first instalment in the trilogy immerses readers in the grim realities of 1980s Britain while celebrating hope in the face of darkness.</p>
|
||||||
<p class="card-text">For Beth, the world is a cold and unforgiving place. Devastation strikes in a single moment, leaving her isolated, shattered, and vulnerable. Alone in the bleak shadows of a city that offers neither refuge nor redemption, she is forced to navigate a relentless cycle of desperation and despair. Every step of her journey tests the limits of her endurance, pushing her into harrowing situations where survival feels like a hollow victory. Beth’s existence is marked by loss, betrayal, and an almost suffocating loneliness that threatens to consume her entirely. Yet, even in the darkest corners of her ordeal, a fragile ember of defiance smoulders within her, a quiet, stubborn refusal to let the world destroy her completely.</p>
|
<p class="card-text">For Beth, the world is a cold and unforgiving place. Devastation strikes in a single moment, leaving her isolated, shattered, and vulnerable. Alone in the bleak shadows of a city that offers neither refuge nor redemption, she is forced to navigate a relentless cycle of desperation and despair. Every step of her journey tests the limits of her endurance, pushing her into harrowing situations where survival feels like a hollow victory. Beth’s existence is marked by loss, betrayal, and an almost suffocating loneliness that threatens to consume her entirely. Yet, even in the darkest corners of her ordeal, a fragile ember of defiance smoulders within her, a quiet, stubborn refusal to let the world destroy her completely.</p>
|
||||||
<p class="card-text">Maggie, by contrast, is a force of nature, a woman who thrives on her unshakable drive and an unrelenting belief in her own power. Behind her fiery red hair and disarming charm lies a storm of determination and ferocity. Maggie doesn’t just live; she races through life, fuelled by a need for speed and the thrill of freedom. Her Triumph TR6 isn’t just a car; it’s an extension of her spirit, sleek, powerful, and unapologetically bold. On the open road, with the engine roaring and the world blurring past her, she feels invincible. But Maggie’s intensity doesn’t stop at the wheel. Her relationships burn just as brightly. As a lover, she is dominant, passionate, and unafraid to embrace her darker desires. While fiercely loving and loyal, Maggie is also formidable; crossing her isn’t a mistake anyone makes twice.</p>
|
<p class="card-text">Maggie, by contrast, is a force of nature, a woman who thrives on her unshakable drive and an unrelenting belief in her own power. Behind her fiery red hair and disarming charm lies a storm of determination and ferocity. Maggie doesn’t just live; she races through life, fuelled by a need for speed and the thrill of freedom. Her Triumph TR6 isn’t just a car; it’s an extension of her spirit, sleek, powerful, and unapologetically bold. On the open road, with the engine roaring and the world blurring past her, she feels invincible. But Maggie’s intensity doesn’t stop at the wheel. Her relationships burn just as brightly. As a lover, she is dominant, passionate, and unafraid to embrace her darker desires. While fiercely loving and loyal, Maggie is also formidable; crossing her isn’t a mistake anyone makes twice.</p>
|
||||||
<p class="card-text">When fate brings Beth and Maggie together, their connection is explosive, a union of two polar opposites that burns with both tenderness and raw power. For Beth, Maggie represents a lifeline, a reminder that love and trust still exist, even in a world that has betrayed her at every turn. For Maggie, Beth awakens a fierce protectiveness and vulnerability she’s rarely allowed herself to feel. Together, they ignite a flame that challenges them to confront their own fears, desires, and limitations.</p>
|
<p class="card-text">When fate brings Beth and Maggie together, their connection is explosive, a union of two polar opposites that burns with both tenderness and raw power. For Beth, Maggie represents a lifeline, a reminder that love and trust still exist, even in a world that has betrayed her at every turn. For Maggie, Beth awakens a fierce protectiveness and vulnerability she’s rarely allowed herself to feel. Together, they ignite a flame that challenges them to confront their own fears, desires, and limitations.</p>
|
||||||
@ -211,11 +212,11 @@
|
|||||||
|
|
||||||
@section Meta{
|
@section Meta{
|
||||||
<MetaTag meta-title="The Alpha Flame: Discovery by Catherine Lynwood"
|
<MetaTag meta-title="The Alpha Flame: Discovery by Catherine Lynwood"
|
||||||
meta-description="Start the journey with 'The Alpha Flame: Discovery' – the gripping first novel in Catherine Lynwood’s trilogy set in 1983 Birmingham. Family secrets, resilience, and a powerful bond between sisters await."
|
meta-description="A gritty 1980s Birmingham crime novel about twin sisters uncovering dark family secrets and surviving abuse. Realistic, powerful, and unflinching — discover The Alpha Flame today."
|
||||||
meta-keywords="The Alpha Flame Discovery, Catherine Lynwood, 1983 novel, twin sisters, suspense fiction, Rubery, Birmingham fiction, historical drama, family secrets"
|
meta-keywords="The Alpha Flame Discovery, Catherine Lynwood, 1983 novel, twin sisters, suspense fiction, Rubery, Birmingham fiction, historical drama, family secrets"
|
||||||
meta-author="Catherine Lynwood"
|
meta-author="Catherine Lynwood"
|
||||||
meta-url="https://www.catherinelynwood.com/the-alpha-flame/discovery"
|
meta-url="https://www.catherinelynwood.com/the-alpha-flame/discovery"
|
||||||
meta-image="https://www.catherinelynwood.com/images/webp/the-alpha-flame-11-600.webp"
|
meta-image="https://www.catherinelynwood.com/images/webp/the-alpha-flame-11-1200.webp"
|
||||||
meta-image-alt="Maggie from 'The Alpha Flame: Discovery' by Catherine Lynwood"
|
meta-image-alt="Maggie from 'The Alpha Flame: Discovery' by Catherine Lynwood"
|
||||||
og-site-name="Catherine Lynwood - The Alpha Flame: Discovery"
|
og-site-name="Catherine Lynwood - The Alpha Flame: Discovery"
|
||||||
article-published-time="@new DateTime(2024, 11, 20)"
|
article-published-time="@new DateTime(2024, 11, 20)"
|
||||||
@ -231,6 +232,7 @@
|
|||||||
"@@type": "Book",
|
"@@type": "Book",
|
||||||
"name": "The Alpha Flame: Discovery",
|
"name": "The Alpha Flame: Discovery",
|
||||||
"alternateName": "The Alpha Flame Book 1",
|
"alternateName": "The Alpha Flame Book 1",
|
||||||
|
"image": "https://www.catherinelynwood.com/images/webp/the-alpha-flame-11-1200.webp",
|
||||||
"author": {
|
"author": {
|
||||||
"@@type": "Person",
|
"@@type": "Person",
|
||||||
"name": "Catherine Lynwood",
|
"name": "Catherine Lynwood",
|
||||||
@ -238,7 +240,8 @@
|
|||||||
},
|
},
|
||||||
"publisher": {
|
"publisher": {
|
||||||
"@@type": "Organization",
|
"@@type": "Organization",
|
||||||
"name": "Catherine Lynwood"
|
"name": "Catherine Lynwood Publishing",
|
||||||
|
"url": "https://www.catherinelynwood.com"
|
||||||
},
|
},
|
||||||
"datePublished": "2025-08-21",
|
"datePublished": "2025-08-21",
|
||||||
"description": "The Alpha Flame: Discovery is a powerful, character-driven novel set in 1983 Birmingham, following Maggie Grant and Beth—two young women separated by fate, reunited by truth, and bound by secrets. As past traumas resurface and danger closes in, their journey through survival, sisterhood, and redemption begins.",
|
"description": "The Alpha Flame: Discovery is a powerful, character-driven novel set in 1983 Birmingham, following Maggie Grant and Beth—two young women separated by fate, reunited by truth, and bound by secrets. As past traumas resurface and danger closes in, their journey through survival, sisterhood, and redemption begins.",
|
||||||
@ -250,29 +253,58 @@
|
|||||||
"@@type": "Book",
|
"@@type": "Book",
|
||||||
"bookFormat": "https://schema.org/Hardcover",
|
"bookFormat": "https://schema.org/Hardcover",
|
||||||
"isbn": "978-1-0682258-0-2",
|
"isbn": "978-1-0682258-0-2",
|
||||||
"name": "The Alpha Flame: Discovery – Hardback"
|
"name": "The Alpha Flame: Discovery – Hardback",
|
||||||
|
"url": "https://www.catherinelynwood.com/the-alpha-flame/discovery",
|
||||||
|
"offers": {
|
||||||
|
"@@type": "Offer",
|
||||||
|
"price": 23.99,
|
||||||
|
"priceCurrency": "GBP",
|
||||||
|
"availability": "https://schema.org/InStock"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"@@type": "Book",
|
"@@type": "Book",
|
||||||
"bookFormat": "https://schema.org/Paperback",
|
"bookFormat": "https://schema.org/Paperback",
|
||||||
"isbn": "978-1-0682258-1-9",
|
"isbn": "978-1-0682258-1-9",
|
||||||
"name": "The Alpha Flame: Discovery – Softback"
|
"name": "The Alpha Flame: Discovery – Paperback (Bookshop Edition)",
|
||||||
|
"url": "https://www.catherinelynwood.com/the-alpha-flame/discovery",
|
||||||
|
"offers": {
|
||||||
|
"@@type": "Offer",
|
||||||
|
"price": 17.99,
|
||||||
|
"priceCurrency": "GBP",
|
||||||
|
"availability": "https://schema.org/InStock"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"@@type": "Book",
|
"@@type": "Book",
|
||||||
"bookFormat": "https://schema.org/Paperback",
|
"bookFormat": "https://schema.org/Paperback",
|
||||||
"isbn": "978-1-0682258-2-6",
|
"isbn": "978-1-0682258-2-6",
|
||||||
"name": "The Alpha Flame: Discovery – Amazon Edition"
|
"name": "The Alpha Flame: Discovery – Amazon Edition",
|
||||||
|
"url": "https://www.catherinelynwood.com/the-alpha-flame/discovery",
|
||||||
|
"offers": {
|
||||||
|
"@@type": "Offer",
|
||||||
|
"price": 14.99,
|
||||||
|
"priceCurrency": "GBP",
|
||||||
|
"availability": "https://schema.org/InStock"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"@@type": "Book",
|
"@@type": "Book",
|
||||||
"bookFormat": "https://schema.org/EBook",
|
"bookFormat": "https://schema.org/EBook",
|
||||||
"isbn": "978-1-0682258-3-3",
|
"isbn": "978-1-0682258-3-3",
|
||||||
"name": "The Alpha Flame: Discovery – eBook"
|
"name": "The Alpha Flame: Discovery – eBook",
|
||||||
|
"url": "https://www.catherinelynwood.com/the-alpha-flame/discovery",
|
||||||
|
"offers": {
|
||||||
|
"@@type": "Offer",
|
||||||
|
"price": 3.95,
|
||||||
|
"priceCurrency": "GBP",
|
||||||
|
"availability": "https://schema.org/InStock"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -47,6 +47,10 @@
|
|||||||
The journey continues in <strong>Reckoning</strong> (Spring 2026) and concludes with <strong>Redemption</strong> (Autumn 2026).
|
The journey continues in <strong>Reckoning</strong> (Spring 2026) and concludes with <strong>Redemption</strong> (Autumn 2026).
|
||||||
Learn more about the full trilogy on the <a asp-controller="TheAlphaFlame" asp-action="Index" class="link-dark fw-semibold">Alpha Flame series page</a>.
|
Learn more about the full trilogy on the <a asp-controller="TheAlphaFlame" asp-action="Index" class="link-dark fw-semibold">Alpha Flame series page</a>.
|
||||||
</p>
|
</p>
|
||||||
|
<p class="mt-4">
|
||||||
|
<h3 class="h4">About The Alpha Flame Trilogy</h3>
|
||||||
|
Catherine Lynwood’s <em>The Alpha Flame trilogy</em> is gripping UK historical fiction set in 1980s Birmingham. These novels combine family drama, dark secrets, and emotional suspense as two sisters fight for truth and redemption. Perfect for readers who enjoy tense, character-driven stories and literary suspense in a richly described real-world setting.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -107,11 +107,12 @@
|
|||||||
"name": "The Alpha Flame: Discovery (Hardback)",
|
"name": "The Alpha Flame: Discovery (Hardback)",
|
||||||
"productID": "ISBN:978-1-0682258-0-2",
|
"productID": "ISBN:978-1-0682258-0-2",
|
||||||
"description": "Hardback edition of Book One in the Alpha Flame Trilogy.",
|
"description": "Hardback edition of Book One in the Alpha Flame Trilogy.",
|
||||||
"url": "https://www.catherinelynwood.com/the-alpha-flame",
|
"url": "https://www.catherinelynwood.com/the-alpha-flame/discovery",
|
||||||
"offers": {
|
"offers": {
|
||||||
"@@type": "Offer",
|
"@@type": "Offer",
|
||||||
"availability": "https://schema.org/InStock",
|
"availability": "https://schema.org/InStock",
|
||||||
"priceCurrency": "GBP"
|
"priceCurrency": "GBP",
|
||||||
|
"price": 23.99
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -119,11 +120,12 @@
|
|||||||
"name": "The Alpha Flame: Discovery (Paperback)",
|
"name": "The Alpha Flame: Discovery (Paperback)",
|
||||||
"productID": "ISBN:978-1-0682258-1-9",
|
"productID": "ISBN:978-1-0682258-1-9",
|
||||||
"description": "Paperback edition of Book One in the Alpha Flame Trilogy.",
|
"description": "Paperback edition of Book One in the Alpha Flame Trilogy.",
|
||||||
"url": "https://www.catherinelynwood.com/the-alpha-flame",
|
"url": "https://www.catherinelynwood.com/the-alpha-flame/discovery",
|
||||||
"offers": {
|
"offers": {
|
||||||
"@@type": "Offer",
|
"@@type": "Offer",
|
||||||
"availability": "https://schema.org/InStock",
|
"availability": "https://schema.org/InStock",
|
||||||
"priceCurrency": "GBP"
|
"priceCurrency": "GBP",
|
||||||
|
"price": 17.99
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -131,11 +133,12 @@
|
|||||||
"name": "The Alpha Flame: Discovery (Amazon Edition)",
|
"name": "The Alpha Flame: Discovery (Amazon Edition)",
|
||||||
"productID": "ISBN:978-1-0682258-2-6",
|
"productID": "ISBN:978-1-0682258-2-6",
|
||||||
"description": "Amazon-exclusive edition of Book One in the Alpha Flame Trilogy.",
|
"description": "Amazon-exclusive edition of Book One in the Alpha Flame Trilogy.",
|
||||||
"url": "https://www.amazon.co.uk/dp/B0D3T5XYTG", // Replace with your actual Amazon product URL
|
"url": "https://www.catherinelynwood.com/the-alpha-flame/discovery",
|
||||||
"offers": {
|
"offers": {
|
||||||
"@@type": "Offer",
|
"@@type": "Offer",
|
||||||
"availability": "https://schema.org/InStock",
|
"availability": "https://schema.org/InStock",
|
||||||
"priceCurrency": "GBP"
|
"priceCurrency": "GBP",
|
||||||
|
"price": 14.99
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -143,21 +146,21 @@
|
|||||||
"name": "The Alpha Flame: Discovery (eBook)",
|
"name": "The Alpha Flame: Discovery (eBook)",
|
||||||
"productID": "ISBN:978-1-0682258-3-3",
|
"productID": "ISBN:978-1-0682258-3-3",
|
||||||
"description": "eBook edition of Book One in the Alpha Flame Trilogy.",
|
"description": "eBook edition of Book One in the Alpha Flame Trilogy.",
|
||||||
"url": "https://www.catherinelynwood.com/the-alpha-flame",
|
"url": "https://www.catherinelynwood.com/the-alpha-flame/discovery",
|
||||||
"offers": {
|
"offers": {
|
||||||
"@@type": "Offer",
|
"@@type": "Offer",
|
||||||
"availability": "https://schema.org/InStock",
|
"availability": "https://schema.org/InStock",
|
||||||
"priceCurrency": "GBP"
|
"priceCurrency": "GBP",
|
||||||
|
"price": 3.95
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"sameAs": [
|
"sameAs": [
|
||||||
"https://www.catherinelynwood.com",
|
"https://www.catherinelynwood.com/the-alpha-flame/discovery",
|
||||||
"https://www.amazon.co.uk/dp/B0D3T5XYTG" // Confirm or add additional relevant links
|
"https://www.amazon.co.uk/dp/B0D3T5XYTG"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
@if (Model.ImageFirst)
|
@if (Model.ImageFirst)
|
||||||
{
|
{
|
||||||
<div class="float-md-start w-md-50 p-3">
|
<div class="float-md-start w-md-50 p-3 text-center">
|
||||||
@if (string.IsNullOrEmpty(Model.VideoUrl))
|
@if (string.IsNullOrEmpty(Model.VideoUrl))
|
||||||
{
|
{
|
||||||
<figure>
|
<figure>
|
||||||
@ -79,13 +79,14 @@
|
|||||||
<!-- Source will be injected later -->
|
<!-- Source will be injected later -->
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
@Html.Raw(Model.ImageDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<div class="float-md-end w-md-50 p-3">
|
<div class="float-md-end w-md-50 p-3 text-center">
|
||||||
@if (string.IsNullOrEmpty(Model.VideoUrl))
|
@if (string.IsNullOrEmpty(Model.VideoUrl))
|
||||||
{
|
{
|
||||||
<figure>
|
<figure>
|
||||||
@ -100,6 +101,7 @@
|
|||||||
<!-- Source will be injected later -->
|
<!-- Source will be injected later -->
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
|
@Html.Raw(Model.ImageDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -204,88 +206,11 @@
|
|||||||
twitter-player-height="@(string.IsNullOrWhiteSpace(Model.VideoUrl) ? null : 80)" />
|
twitter-player-height="@(string.IsNullOrWhiteSpace(Model.VideoUrl) ? null : 80)" />
|
||||||
|
|
||||||
|
|
||||||
@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 imageJson = string.Empty;
|
|
||||||
string videoJson = string.Empty;
|
|
||||||
string description;
|
|
||||||
if (!string.IsNullOrWhiteSpace(Model.AudioTeaserUrl))
|
|
||||||
{
|
|
||||||
description = $"Listen to my new blog entry: {Model.Title} by Catherine Lynwood";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
description = $"Read my latest blog post: {Model.Title} by Catherine Lynwood";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var contentTopPlainText = StripHtml(Model.ContentTop);
|
|
||||||
var contentBottomPlainText = StripHtml(Model.ContentBottom);
|
|
||||||
|
|
||||||
// Image JSON-LD block if ImageUrl is available
|
|
||||||
if (!string.IsNullOrWhiteSpace(Model.ImageUrl))
|
|
||||||
{
|
|
||||||
string imageUrl = $"https://www.catherinelynwood.com/images/webp/{Model.DefaultWebpImage}";
|
|
||||||
imageJson = $@",
|
|
||||||
""image"": {{
|
|
||||||
""@type"": ""ImageObject"",
|
|
||||||
""url"": ""{imageUrl}"",
|
|
||||||
""description"": ""{Model.ImageDescription}""
|
|
||||||
}}";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Video JSON-LD block if VideoUrl is available
|
|
||||||
if (!string.IsNullOrWhiteSpace(Model.VideoUrl))
|
|
||||||
{
|
|
||||||
// You can add extra properties here if your Blog class supports them in future
|
|
||||||
videoJson = $@",
|
|
||||||
""video"": {{
|
|
||||||
""@type"": ""VideoObject"",
|
|
||||||
""name"": ""{Model.Title} – Video Teaser"",
|
|
||||||
""description"": ""Atmospheric teaser video for {Model.Title}."",
|
|
||||||
""thumbnailUrl"": ""https://www.catherinelynwood.com/images/webp/{Model.DefaultWebpImage}"",
|
|
||||||
""uploadDate"": ""{Model.PublishDate:yyyy-MM-dd}"",
|
|
||||||
""contentUrl"": ""{Model.VideoUrl}"",
|
|
||||||
""embedUrl"": ""{Model.VideoUrl}""
|
|
||||||
}}";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble the final JSON-LD with conditional blocks
|
|
||||||
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"": ""{blogUrl}""{imageJson}{videoJson},
|
|
||||||
""interactionStatistic"": {{
|
|
||||||
""@type"": ""InteractionCounter"",
|
|
||||||
""interactionType"": {{
|
|
||||||
""@type"": ""http://schema.org/LikeAction""
|
|
||||||
}},
|
|
||||||
""userInteractionCount"": {Model.Likes}
|
|
||||||
}}
|
|
||||||
}}";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
@Html.Raw(blogPostJsonLd)
|
@Html.Raw(Model.SchemaJsonLd)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
ul {
|
ul {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
|||||||
131
CatherineLynwood/Views/TheAlphaFlame/Giveaways.cshtml
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
@{
|
||||||
|
ViewData["Title"] = "Giveaways - The Alpha Flame";
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="py-3 text-center">
|
||||||
|
<div class="container">
|
||||||
|
<div class="card bg-dark text-white">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="display-4 fw-bold">Win a Collector’s Edition</h1>
|
||||||
|
<p class="lead mb-4">A special chance to own <em>The Alpha Flame: Discovery</em> in a limited edition, signed or delivered to you.</p>
|
||||||
|
<a href="/newsletter/signup" class="btn btn-primary btn-lg">Enter Now</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="py-3">
|
||||||
|
<div class="container">
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="h4 fw-bold mb-3">How to Enter</h2>
|
||||||
|
<ul class="list-unstyled">
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fad fa-check-circle text-success me-2"></i>
|
||||||
|
Sign up for my newsletter
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fad fa-check-circle text-success me-2"></i>
|
||||||
|
Entries close on <strong>31st August 2025</strong>
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fad fa-check-circle text-success me-2"></i>
|
||||||
|
Open to UK, US, Canada, and Australia
|
||||||
|
</li>
|
||||||
|
<li class="mb-2">
|
||||||
|
<i class="fad fa-check-circle text-success me-2"></i>
|
||||||
|
Winner announced 7th September 2025 via email and on this page
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p class="mt-3">
|
||||||
|
<strong>What you’ll win:</strong>
|
||||||
|
UK winners receive a signed Collector’s Edition mailed by me.
|
||||||
|
Winners in the US, Canada, or Australia receive an unsigned Collector’s Edition printed and shipped locally.
|
||||||
|
</p>
|
||||||
|
<p class="small text-muted">No purchase necessary. You can unsubscribe at any time.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="py-3">
|
||||||
|
<div class="container">
|
||||||
|
<div class="card bg-dark text-white">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="h4 fw-bold text-center mb-4">The Prize</h2>
|
||||||
|
<div class="row text-center">
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<responsive-image src="the-alpha-flame-11.png" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" alt="Signed Collector’s Edition" display-width-percentage="50"></responsive-image>
|
||||||
|
<p class="mt-2">Signed by the author (UK only)</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<responsive-image src="the-alpha-flame-11.png" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" alt="Exclusive cover detail" display-width-percentage="50"></responsive-image>
|
||||||
|
<p class="mt-2">Exclusive cover design</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<responsive-image src="the-alpha-flame-11.png" class="img-fluid rounded-5 border border-3 border-dark shadow-lg" alt="Interior preview" display-width-percentage="50"></responsive-image>
|
||||||
|
<p class="mt-2">Premium print quality</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="py-5 text-center">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="h4 fw-bold mb-3">Don’t Miss Out</h2>
|
||||||
|
<p class="mb-4">Sign up today for your chance to win. Plus, get exclusive updates, early previews, and behind-the-scenes content.</p>
|
||||||
|
<a href="/newsletter/signup" class="btn btn-dark btn-lg">Enter the Giveaway</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@section Meta{
|
||||||
|
<MetaTag meta-title="Win a Collector’s Edition of The Alpha Flame: Discovery – Catherine Lynwood"
|
||||||
|
meta-description="Enter to win a limited Collector’s Edition of The Alpha Flame: Discovery. UK winners receive a signed copy; US, Canada, and Australia winners receive an unsigned copy shipped locally. Entries close 31st August 2025."
|
||||||
|
meta-keywords="The Alpha Flame giveaway, signed book giveaway, Catherine Lynwood, book contest, limited edition book"
|
||||||
|
meta-author="Catherine Lynwood"
|
||||||
|
meta-url="https://www.catherinelynwood.com/the-alpha-flame/giveaways"
|
||||||
|
meta-image="https://www.catherinelynwood.com/images/alpha-flame-collector-edition.webp"
|
||||||
|
meta-image-alt="The Alpha Flame: Discovery Collector’s Edition Giveaway"
|
||||||
|
og-site-name="Catherine Lynwood – The Alpha Flame"
|
||||||
|
article-published-time="@new DateTime(2025, 07, 01)" />
|
||||||
|
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@@context": "https://schema.org",
|
||||||
|
"@@type": "Offer",
|
||||||
|
"name": "The Alpha Flame: Discovery Collector’s Edition Giveaway",
|
||||||
|
"description": "Enter to win a Collector’s Edition of The Alpha Flame: Discovery. UK winners receive a signed copy, while US, Canada, and Australia winners receive an unsigned copy shipped locally. Entries close 31st August 2025.",
|
||||||
|
"url": "https://www.catherinelynwood.com/the-alpha-flame/giveaways",
|
||||||
|
"availabilityStarts": "2025-07-01",
|
||||||
|
"availabilityEnds": "2025-08-31",
|
||||||
|
"eligibleRegion": [
|
||||||
|
{
|
||||||
|
"@@type": "Country",
|
||||||
|
"name": "United Kingdom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@@type": "Country",
|
||||||
|
"name": "United States"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@@type": "Country",
|
||||||
|
"name": "Canada"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@@type": "Country",
|
||||||
|
"name": "Australia"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"seller": {
|
||||||
|
"@@type": "Person",
|
||||||
|
"name": "Catherine Lynwood",
|
||||||
|
"url": "https://www.catherinelynwood.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
}
|
||||||
@ -142,85 +142,61 @@
|
|||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
@section Scripts{
|
@section Scripts {
|
||||||
<script>
|
<script>
|
||||||
const player = new Plyr('audio');
|
const player = new Plyr('audio');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(Model.VideoUrl))
|
||||||
|
{
|
||||||
|
<script>
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
const video = document.getElementById("heroVideo");
|
||||||
|
const source = document.createElement("source");
|
||||||
|
source.src = "/videos/@Model.VideoUrl";
|
||||||
|
source.type = "video/mp4";
|
||||||
|
video.appendChild(source);
|
||||||
|
video.load(); // Triggers actual file load
|
||||||
|
video.play().catch(err => {
|
||||||
|
console.warn("Autoplay failed:", err);
|
||||||
|
});
|
||||||
|
}, 2000); // Adjust delay (e.g., 1000–3000ms) based on your needs
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@section Meta {
|
@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.">
|
<MetaTag meta-title="@Model.Title"
|
||||||
<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-description="@Model.IndexText"
|
||||||
<meta name="author" content="Catherine Lynwood">
|
meta-keywords="Catherine Lynwood blog, The Alpha Flame, psychological thriller, indie fiction, 1980s fiction, dark secrets, strong female characters"
|
||||||
|
meta-author="Catherine Lynwood"
|
||||||
|
meta-url="https://www.catherinelynwood.com/@Model.BlogUrl"
|
||||||
|
meta-image="@(string.IsNullOrWhiteSpace(Model.ImageUrl) ? null : $"https://www.catherinelynwood.com/images/webp/{Model.DefaultWebpImage}")"
|
||||||
|
meta-image-alt="@Model.ImageAlt"
|
||||||
|
og-site-name="Catherine Lynwood - The Alpha Flame"
|
||||||
|
article-published-time="@Model.PublishDate"
|
||||||
|
article-modified-time="@DateTime.UtcNow"
|
||||||
|
twitter-card-type="@(string.IsNullOrWhiteSpace(Model.VideoUrl) ? "summary_large_image" : "player")"
|
||||||
|
twitter-site-handle="@@CathLynwood"
|
||||||
|
twitter-creator-handle="@@CathLynwood"
|
||||||
|
twitter-video-url="@(string.IsNullOrWhiteSpace(Model.VideoUrl) ? null : Model.VideoUrl)"
|
||||||
|
twitter-player-width="@(string.IsNullOrWhiteSpace(Model.VideoUrl) ? null : 480)"
|
||||||
|
twitter-player-height="@(string.IsNullOrWhiteSpace(Model.VideoUrl) ? null : 80)" />
|
||||||
|
|
||||||
@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">
|
<script type="application/ld+json">
|
||||||
@Html.Raw(blogPostJsonLd)
|
@Html.Raw(Model.SchemaJsonLd)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
CatherineLynwood/redirects.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"/the-alpha-flame/chapters/chapter-2-maggie": "/the-alpha-flame/discovery/chapters/chapter-2-maggie",
|
||||||
|
"/the-alpha-flame/chapters/chapter-1-beth": "/the-alpha-flame/discovery/chapters/chapter-1-beth",
|
||||||
|
"/sisterhood-blog-highlight": "/the-alpha-flame/blog/sisterhood-survival-self-discovery-books",
|
||||||
|
"/the-alpha-flame/front-cover": "/the-alpha-flame/discovery",
|
||||||
|
"/the-alpah-flame/blog/1983-birmingham-behind-the-alpha-flame": "/the-alpha-flame/blog/1983-birmingham-behind-the-alpha-flame",
|
||||||
|
"/the-alpah-flame/blog/sisterhood-survival-self-discovery-books": "/the-alpha-flame/blog/sisterhood-survival-self-discovery-books",
|
||||||
|
"/the-alpah-flame/blog/who-is-maggie-grant-alpha-flame": "/the-alpha-flame/blog/who-is-maggie-grant-alpha-flame",
|
||||||
|
"/the-alpah-flame/blog/why-i-wrote-the-alpha-flame": "/the-alpha-flame/blog/why-i-wrote-the-alpha-flame",
|
||||||
|
"/the-alpah-flame/blog/books-like-verity": "/the-alpha-flame/blog/books-like-verity",
|
||||||
|
"/TheAlphaFlame/Discovery": "/the-alpha-flame/discovery",
|
||||||
|
"/the-alpha-flame/meet-the-characters": "/the-alpha-flame/characters",
|
||||||
|
"/the-alpha-flame/maggie-grant": "/the-alpha-flame/characters/maggie-grant",
|
||||||
|
"/the-alpha-flame/characters/beth": "/the-alpha-flame/characters/beth-fletcher"
|
||||||
|
}
|
||||||
BIN
CatherineLynwood/wwwroot/audio/meet-catherine-qa.mp3
Normal file
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 26 KiB |
BIN
CatherineLynwood/wwwroot/images/jpg/meet-catherine-qa-400.jpg
Normal file
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 36 KiB |
BIN
CatherineLynwood/wwwroot/images/meet-catherine-qa.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
BIN
CatherineLynwood/wwwroot/images/up-and-coming-indie-author.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 4.2 MiB |
BIN
CatherineLynwood/wwwroot/images/webp/asking-for-reviews-768.webp
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
CatherineLynwood/wwwroot/images/webp/asking-for-reviews-992.webp
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 3.0 MiB |
|
After Width: | Height: | Size: 4.7 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 234 KiB |
|
After Width: | Height: | Size: 355 KiB |
|
After Width: | Height: | Size: 496 KiB |
|
After Width: | Height: | Size: 985 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 130 KiB |
|
After Width: | Height: | Size: 192 KiB |
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 262 KiB |
|
After Width: | Height: | Size: 364 KiB |
|
After Width: | Height: | Size: 491 KiB |
|
After Width: | Height: | Size: 534 KiB |
|
After Width: | Height: | Size: 738 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-172.webp
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-230.webp
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-288.webp
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-297.webp
Normal file
|
After Width: | Height: | Size: 129 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-360.webp
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-384.webp
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-400.webp
Normal file
|
After Width: | Height: | Size: 233 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-420.webp
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-496.webp
Normal file
|
After Width: | Height: | Size: 350 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-576.webp
Normal file
|
After Width: | Height: | Size: 467 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-600.webp
Normal file
|
After Width: | Height: | Size: 506 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-700.webp
Normal file
|
After Width: | Height: | Size: 694 KiB |
BIN
CatherineLynwood/wwwroot/images/webp/meet-catherine-qa-960.webp
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 475 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 204 KiB |
|
After Width: | Height: | Size: 228 KiB |
|
After Width: | Height: | Size: 244 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 354 KiB |
|
After Width: | Height: | Size: 459 KiB |
|
After Width: | Height: | Size: 494 KiB |
|
After Width: | Height: | Size: 661 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 238 KiB |
|
After Width: | Height: | Size: 272 KiB |
|
After Width: | Height: | Size: 295 KiB |
|
After Width: | Height: | Size: 327 KiB |
|
After Width: | Height: | Size: 457 KiB |
|
After Width: | Height: | Size: 616 KiB |
|
After Width: | Height: | Size: 669 KiB |
|
After Width: | Height: | Size: 918 KiB |
|
After Width: | Height: | Size: 1.6 MiB |