diff --git a/CatherineLynwood/Controllers/BuyController.cs b/CatherineLynwood/Controllers/BuyController.cs new file mode 100644 index 0000000..f82dbc0 --- /dev/null +++ b/CatherineLynwood/Controllers/BuyController.cs @@ -0,0 +1,240 @@ +using CatherineLynwood.Models; +using CatherineLynwood.Services; + +using Microsoft.AspNetCore.Mvc; + +using System.Web; + +namespace CatherineLynwood.Controllers +{ + [Route("go")] + public class BuyController : Controller + { + private readonly DataAccess _dataAccess; + // Replace with DB/service + private static readonly Dictionary Links = new() + { + // --- Ingram direct (GB/US only) --- + ["ingram-hardback-gb"] = new BuyLink + { + Slug = "ingram-hardback-gb", + Url = "https://shop.ingramspark.com/b/084?params=GC1p1c8b66Rhfoy6Tq97SJmmhdZSEYuxBcCY5zxNstO", + Retailer = "Ingram", + Format = "Hardback", + CountryGroup = "GB" + }, + ["ingram-paperback-gb"] = new BuyLink + { + Slug = "ingram-paperback-gb", + Url = "https://shop.ingramspark.com/b/084?params=6easpH54PaugzXFKdF4Tu4Izb0cvkMqbj3ZNlaYBKMJ", + Retailer = "Ingram", + Format = "Paperback", + CountryGroup = "GB" + }, + ["ingram-hardback-us"] = new BuyLink + { + Slug = "ingram-hardback-us", + Url = "https://shop.ingramspark.com/b/084?params=GC1p1c8b66Rhfoy6Tq97SJmmhdZSEYuxBcCY5zxNstO", + Retailer = "Ingram", + Format = "Hardback", + CountryGroup = "US" + }, + ["ingram-paperback-us"] = new BuyLink + { + Slug = "ingram-paperback-us", + Url = "https://shop.ingramspark.com/b/084?params=6easpH54PaugzXFKdF4Tu4Izb0cvkMqbj3ZNlaYBKMJ", + Retailer = "Ingram", + Format = "Paperback", + CountryGroup = "US" + }, + + // --- Amazon (all countries) --- + ["amazon-hardback-gb"] = new BuyLink + { + Slug = "amazon-hardback-gb", + Url = "https://www.amazon.co.uk/dp/1068225807", + Retailer = "Amazon", + Format = "Hardback", + CountryGroup = "GB" + }, + ["amazon-paperback-gb"] = new BuyLink + { + Slug = "amazon-paperback-gb", + Url = "https://www.amazon.co.uk/dp/1068225815", + Retailer = "Amazon", + Format = "Paperback", + CountryGroup = "GB" + }, + ["amazon-kindle-gb"] = new BuyLink + { + Slug = "amazon-kindle-gb", + Url = "https://www.amazon.co.uk/dp/B0FBS427VD", + Retailer = "Amazon", + Format = "Kindle", + CountryGroup = "GB" + }, + + ["amazon-hardback-us"] = new BuyLink + { + Slug = "amazon-hardback-us", + Url = "https://www.amazon.com/dp/1068225807", + Retailer = "Amazon", + Format = "Hardback", + CountryGroup = "US" + }, + ["amazon-paperback-us"] = new BuyLink + { + Slug = "amazon-paperback-us", + Url = "https://www.amazon.com/dp/1068225815", + Retailer = "Amazon", + Format = "Paperback", + CountryGroup = "US" + }, + ["amazon-kindle-us"] = new BuyLink + { + Slug = "amazon-kindle-us", + Url = "https://www.amazon.com/dp/B0FBS427VD", + Retailer = "Amazon", + Format = "Kindle", + CountryGroup = "US" + }, + + ["amazon-hardback-ca"] = new BuyLink + { + Slug = "amazon-hardback-ca", + Url = "https://www.amazon.ca/dp/1068225807", + Retailer = "Amazon", + Format = "Hardback", + CountryGroup = "CA" + }, + ["amazon-paperback-ca"] = new BuyLink + { + Slug = "amazon-paperback-ca", + Url = "https://www.amazon.ca/dp/1068225815", + Retailer = "Amazon", + Format = "Paperback", + CountryGroup = "CA" + }, + ["amazon-kindle-ca"] = new BuyLink + { + Slug = "amazon-kindle-ca", + Url = "https://www.amazon.ca/dp/B0FBS427VD", + Retailer = "Amazon", + Format = "Kindle", + CountryGroup = "CA" + }, + + ["amazon-hardback-au"] = new BuyLink + { + Slug = "amazon-hardback-au", + Url = "https://www.amazon.com.au/dp/1068225807", + Retailer = "Amazon", + Format = "Hardback", + CountryGroup = "AU" + }, + ["amazon-paperback-au"] = new BuyLink + { + Slug = "amazon-paperback-au", + Url = "https://www.amazon.com.au/dp/1068225815", + Retailer = "Amazon", + Format = "Paperback", + CountryGroup = "AU" + }, + ["amazon-kindle-au"] = new BuyLink + { + Slug = "amazon-kindle-au", + Url = "https://www.amazon.com.au/dp/B0FBS427VD", + Retailer = "Amazon", + Format = "Kindle", + CountryGroup = "AU" + }, + + // --- National retailers --- + ["waterstones-hardback-gb"] = new BuyLink + { + Slug = "waterstones-hardback-gb", + Url = "https://www.waterstones.com/book/the-alpha-flame/catherine-lynwood/9781068225802", + Retailer = "Waterstones", + Format = "Hardback", + CountryGroup = "GB" + }, + ["waterstones-paperback-gb"] = new BuyLink + { + Slug = "waterstones-paperback-gb", + Url = "https://www.waterstones.com/book/the-alpha-flame/catherine-lynwood/9781068225819", + Retailer = "Waterstones", + Format = "Paperback", + CountryGroup = "GB" + }, + + ["bn-hardback-us"] = new BuyLink + { + Slug = "bn-hardback-us", + Url = "https://www.barnesandnoble.com/s/9781068225802", + Retailer = "Barnes & Noble", + Format = "Hardback", + CountryGroup = "US" + }, + ["bn-paperback-us"] = new BuyLink + { + Slug = "bn-paperback-us", + Url = "https://www.barnesandnoble.com/s/9781068225819", + Retailer = "Barnes & Noble", + Format = "Paperback", + CountryGroup = "US" + } + }; + + public BuyController(DataAccess dataAccess) + { + _dataAccess = dataAccess; + } + + [HttpGet("{slug}")] + public async Task Go(string slug) + { + if (!Links.TryGetValue(slug, out var link)) + return NotFound(); + + var referer = Request.Headers["Referer"].ToString(); + var ua = Request.Headers["User-Agent"].ToString(); + var ip = HttpContext.Connection.RemoteIpAddress?.ToString(); + var qs = Request.QueryString.HasValue ? Request.QueryString.Value : string.Empty; + var page = referer; // or: $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Request.Path}" + var countryQS = Request.Query["country"].ToString(); // if you pass ?country=GB for testing + + // session id cookie + var sessionId = Request.Cookies["sid"] ?? Guid.NewGuid().ToString("N"); + if (!Request.Cookies.ContainsKey("sid")) + { + Response.Cookies.Append("sid", sessionId, new CookieOptions + { + HttpOnly = true, + SameSite = SameSiteMode.Lax, + Secure = true, + MaxAge = TimeSpan.FromDays(365) + }); + } + + // Save click (don’t block the redirect if this fails) + _ = await _dataAccess.SaveBuyClick( + dateTimeUtc: DateTime.UtcNow, + slug: link.Slug, + retailer: link.Retailer, + format: link.Format, + countryGroup: link.CountryGroup, + ip: ip, + country: string.IsNullOrWhiteSpace(countryQS) ? null : countryQS, // optional override + userAgent: ua, + referer: referer, + page: page, + sessionId: sessionId, + queryString: qs, + destinationUrl: link.Url + ); + + return Redirect(link.Url); // 302 + } + + } +} diff --git a/CatherineLynwood/Controllers/DiscoveryController.cs b/CatherineLynwood/Controllers/DiscoveryController.cs index c10292f..14a0a66 100644 --- a/CatherineLynwood/Controllers/DiscoveryController.cs +++ b/CatherineLynwood/Controllers/DiscoveryController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using System.Globalization; +using System.Text.Json; using System.Text.RegularExpressions; namespace CatherineLynwood.Controllers @@ -74,7 +75,7 @@ namespace CatherineLynwood.Controllers Reviews reviews = await _dataAccess.GetReviewsAsync(); reviews.SchemaJsonLd = GenerateBookSchemaJsonLd(reviews, 3); - return View(reviews); + return View("Index1", reviews); } [BookAccess(1, 1)] @@ -107,11 +108,50 @@ namespace CatherineLynwood.Controllers return View(); } + [BookAccess(1, 1)] + [Route("extras/soundtrack")] + public async Task Soundtrack() + { + List soundtrackTrackModels = await _dataAccess.GetSoundtrack(); + + return View(soundtrackTrackModels); + } + [Route("trailer")] public async Task Trailer() { + var ip = HttpContext.Connection.RemoteIpAddress?.ToString(); + var userAgent = HttpContext.Request.Headers["User-Agent"].ToString(); + + //ip = "77.104.168.236"; + + string country = "Unknown"; + using (var client = new HttpClient()) + { + try + { + var response = await client.GetStringAsync($"http://ip-api.com/json/{ip}"); + var json = JsonDocument.Parse(response); + country = json.RootElement.GetProperty("countryCode").GetString(); + + if (country == "GB") + { + country = "UK"; + } + } + catch (Exception ex) + { + // Fail silently + } + } + FlagSupportViewModel flagSupportViewModel = await _dataAccess.GetTrailerLikes(); + foreach (var flag in flagSupportViewModel.FlagCounts) + { + flag.Selected = flag.Key.ToLower() == country.ToLower(); + } + return View(flagSupportViewModel); } diff --git a/CatherineLynwood/Controllers/SitemapController.cs b/CatherineLynwood/Controllers/SitemapController.cs index d738c55..6c58186 100644 --- a/CatherineLynwood/Controllers/SitemapController.cs +++ b/CatherineLynwood/Controllers/SitemapController.cs @@ -42,6 +42,7 @@ namespace CatherineLynwood.Controllers new SitemapEntry { Url = Url.Action("Characters", "TheAlphaFlame", 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("Giveaways", "TheAlphaFlame", null, Request.Scheme).TrimEnd('/'), LastModified = DateTime.UtcNow }, + new SitemapEntry { Url = Url.Action("StoryMap", "TheAlphaFlame", null, Request.Scheme).TrimEnd('/'), LastModified = DateTime.UtcNow }, new SitemapEntry { Url = Url.Action("HowToBuy", "Discovery", 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", "Discovery", null, Request.Scheme).TrimEnd('/'), LastModified = DateTime.UtcNow }, diff --git a/CatherineLynwood/Controllers/TheAlphaFlameController.cs b/CatherineLynwood/Controllers/TheAlphaFlameController.cs index f06f3db..25451a8 100644 --- a/CatherineLynwood/Controllers/TheAlphaFlameController.cs +++ b/CatherineLynwood/Controllers/TheAlphaFlameController.cs @@ -256,6 +256,12 @@ namespace CatherineLynwood.Controllers return View(); } + [Route("story-map")] + public IActionResult StoryMap() + { + return View(); + } + #endregion Public Methods #region Private Methods diff --git a/CatherineLynwood/Models/BuyLink.cs b/CatherineLynwood/Models/BuyLink.cs new file mode 100644 index 0000000..f90031d --- /dev/null +++ b/CatherineLynwood/Models/BuyLink.cs @@ -0,0 +1,19 @@ +namespace CatherineLynwood.Models +{ + public class BuyLink + { + #region Public Properties + + public string CountryGroup { get; set; } + + public string Format { get; set; } + + public string Retailer { get; set; } + + public string Slug { get; set; } + + public string Url { get; set; } + + #endregion Public Properties + } +} \ No newline at end of file diff --git a/CatherineLynwood/Models/FlagSupportViewModel.cs b/CatherineLynwood/Models/FlagSupportViewModel.cs index c200409..10aacd4 100644 --- a/CatherineLynwood/Models/FlagSupportViewModel.cs +++ b/CatherineLynwood/Models/FlagSupportViewModel.cs @@ -2,7 +2,16 @@ { public class FlagSupportViewModel { - public Dictionary FlagCounts { get; set; } = new(); + public List FlagCounts { get; set; } = new(); + } + + public class FlagCount + { + public string Key { get; set; } + + public int Value { get; set; } + + public bool Selected { get; set; } } } diff --git a/CatherineLynwood/Models/SoundTrackModel.cs b/CatherineLynwood/Models/SoundTrackModel.cs new file mode 100644 index 0000000..fb672bc --- /dev/null +++ b/CatherineLynwood/Models/SoundTrackModel.cs @@ -0,0 +1,27 @@ +namespace CatherineLynwood.Models +{ + public class SoundtrackTrackModel + { + #region Public Properties + + public string AudioUrl { get; set; } + + public string Chapter { get; set; } + + // e.g., "Chapter 18" + public string Description { get; set; } + + // short blurb + public string ImageUrl { get; set; } + + // artwork or suggestive image + // mp3/ogg + public string LyricsHtml { get; set; } + + public string Title { get; set; } + + #endregion Public Properties + + // fallback if no HTML + } +} \ No newline at end of file diff --git a/CatherineLynwood/Services/DataAccess.cs b/CatherineLynwood/Services/DataAccess.cs index 2f71ac9..ccc743e 100644 --- a/CatherineLynwood/Services/DataAccess.cs +++ b/CatherineLynwood/Services/DataAccess.cs @@ -65,6 +65,64 @@ namespace CatherineLynwood.Services return visible; } + public async Task SaveBuyClick( + DateTime dateTimeUtc, + string slug, + string retailer, + string format, + string countryGroup, + string ip, + string country, + string userAgent, + string referer, + string page, // the page on your site where the click happened + string sessionId, + string queryString, // original request query (e.g., utms, country override) + string destinationUrl // the final retailer URL you redirect to + ) + { + bool success = true; + + using (SqlConnection conn = new SqlConnection(_connectionString)) + using (SqlCommand cmd = new SqlCommand()) + { + try + { + await conn.OpenAsync(); + cmd.Connection = conn; + cmd.CommandType = CommandType.StoredProcedure; + cmd.CommandText = "SaveBuyClick"; + + // Required + cmd.Parameters.AddWithValue("@DateTimeUtc", dateTimeUtc); + cmd.Parameters.AddWithValue("@Slug", (object?)slug ?? DBNull.Value); + cmd.Parameters.AddWithValue("@Retailer", (object?)retailer ?? DBNull.Value); + cmd.Parameters.AddWithValue("@Format", (object?)format ?? DBNull.Value); + cmd.Parameters.AddWithValue("@CountryGroup", (object?)countryGroup ?? DBNull.Value); + + // Signals / attribution + cmd.Parameters.AddWithValue("@IP", (object?)ip ?? DBNull.Value); + cmd.Parameters.AddWithValue("@Country", (object?)country ?? DBNull.Value); + cmd.Parameters.AddWithValue("@UserAgent", (object?)userAgent ?? DBNull.Value); + cmd.Parameters.AddWithValue("@Referer", (object?)referer ?? DBNull.Value); + cmd.Parameters.AddWithValue("@Page", (object?)page ?? DBNull.Value); + cmd.Parameters.AddWithValue("@SessionId", (object?)sessionId ?? DBNull.Value); + cmd.Parameters.AddWithValue("@QueryString", (object?)queryString ?? DBNull.Value); + cmd.Parameters.AddWithValue("@DestinationUrl", (object?)destinationUrl ?? DBNull.Value); + + await cmd.ExecuteNonQueryAsync(); + } + catch (Exception) + { + success = false; + // optional: log the exception somewhere central + } + } + + return success; + } + + public async Task Addhoneypot(DateTime dateTime, string ip, string country, string userAgent, string referer) { bool success = true; @@ -131,6 +189,48 @@ namespace CatherineLynwood.Services return likes; } + public async Task> GetSoundtrack() + { + List soundtrackTrackModels = new List(); + + using (SqlConnection conn = new SqlConnection(_connectionString)) + { + using (SqlCommand cmd = new SqlCommand()) + { + try + { + await conn.OpenAsync(); + cmd.Connection = conn; + cmd.CommandType = CommandType.StoredProcedure; + cmd.CommandText = "GetSoundtrack"; + + using (SqlDataReader rdr = await cmd.ExecuteReaderAsync()) + { + while (await rdr.ReadAsync()) + { + soundtrackTrackModels.Add(new SoundtrackTrackModel + { + AudioUrl = GetDataString(rdr, "AudioUrl"), + Chapter = GetDataString(rdr, "Chapter"), + Description = GetDataString(rdr, "Description"), + ImageUrl = GetDataString(rdr, "ImageUrl"), + LyricsHtml = GetDataString(rdr, "LyricsHtml"), + Title = GetDataString(rdr, "Title") + }); + } + } + } + catch (Exception ex) + { + + } + } + } + + + return soundtrackTrackModels; + } + public async Task GetTrailerLikes() { FlagSupportViewModel flagSupportViewModel = new FlagSupportViewModel(); @@ -153,7 +253,11 @@ namespace CatherineLynwood.Services string country = GetDataString(rdr, "Country"); int likes = GetDataInt(rdr, "Likes"); - flagSupportViewModel.FlagCounts.Add(country, likes); + flagSupportViewModel.FlagCounts.Add(new FlagCount + { + Key = country, + Value = likes + }); } } } diff --git a/CatherineLynwood/Views/Discovery/Extras.cshtml b/CatherineLynwood/Views/Discovery/Extras.cshtml index c526bf1..5deb070 100644 --- a/CatherineLynwood/Views/Discovery/Extras.cshtml +++ b/CatherineLynwood/Views/Discovery/Extras.cshtml @@ -43,6 +43,14 @@ View Scrapbook + +
+
+
Discovery Soundtrack
+

Have a listen to The Alpha Flame soundtrack. A selection of original songs written by me and put to music.

+ Soundtrack +
+
} @if (accessLevel >= 3) { diff --git a/CatherineLynwood/Views/Discovery/HowToBuy.cshtml b/CatherineLynwood/Views/Discovery/HowToBuy.cshtml index ad311ad..220a810 100644 --- a/CatherineLynwood/Views/Discovery/HowToBuy.cshtml +++ b/CatherineLynwood/Views/Discovery/HowToBuy.cshtml @@ -44,15 +44,13 @@

This version is designed for local bookstores and global retailers.

+ + + 📦 Buy Direct (Save & Support Author) + Buy on Amazon - - - - 📦 Buy Direct (Save & Support Author) - -

ISBN 978-1-0682258-1-9

@@ -67,15 +65,14 @@

A premium collector’s hardback edition, available via bookstores and online.

+ + + 💎 Buy Direct (Save & Support Author) + Buy on Amazon - - - 💎 Buy Direct (Save & Support Author) - -

ISBN 978-1-0682258-0-2

@@ -86,14 +83,12 @@
Audiobook (AI-Read)
-
-

Listen to the entire book for free on ElevenLabs:

+
+

Listen to the entire book for free on ElevenLabs (Elevenlabs subscription required):

🎧 Listen on ElevenLabs
- - Scan to listen
@@ -140,11 +135,29 @@ break; } - document.getElementById("kindleLink").setAttribute("href", kindleLink); - document.getElementById("paperbackLink").setAttribute("href", paperbackLink); - document.getElementById("hardbackLink").setAttribute("href", hardbackLink); - document.getElementById("extraRetailers").innerHTML = extraRetailers; - document.getElementById("extraRetailersHardback").innerHTML = extraRetailersHardback; + // Set Amazon + retailer content + const elKindle = document.getElementById("kindleLink"); + const elPbAmazon = document.getElementById("paperbackLink"); + const elHbAmazon = document.getElementById("hardbackLink"); + const elExtra = document.getElementById("extraRetailers"); + const elExtraHb = document.getElementById("extraRetailersHardback"); + + if (elKindle) elKindle.setAttribute("href", kindleLink); + if (elPbAmazon) elPbAmazon.setAttribute("href", paperbackLink); + if (elHbAmazon) elHbAmazon.setAttribute("href", hardbackLink); + if (elExtra) elExtra.innerHTML = extraRetailers; + if (elExtraHb) elExtraHb.innerHTML = extraRetailersHardback; + + // Show IngramSpark only in GB/US; hide elsewhere + const showIngram = country === "GB" || country === "US"; + ["paperbackLinkSelf", "hardbackLinkSelf"].forEach(id => { + const el = document.getElementById(id); + if (!el) return; + el.classList.toggle("d-none", !showIngram); // add when false, remove when true + }); + }) + .catch(() => { + // If the geo lookup fails, leave links as-is }); diff --git a/CatherineLynwood/Views/Discovery/Index.cshtml b/CatherineLynwood/Views/Discovery/Index.cshtml index dc2726c..133d738 100644 --- a/CatherineLynwood/Views/Discovery/Index.cshtml +++ b/CatherineLynwood/Views/Discovery/Index.cshtml @@ -45,18 +45,9 @@
-
-

Buy the Book

-
- - Buy Kindle Edition - -

- Want paperback, hardback, or audiobook options? -

- - See All Buying Options + + Buy the Book
diff --git a/CatherineLynwood/Views/Discovery/Index1.cshtml b/CatherineLynwood/Views/Discovery/Index1.cshtml new file mode 100644 index 0000000..7f2bd92 --- /dev/null +++ b/CatherineLynwood/Views/Discovery/Index1.cshtml @@ -0,0 +1,469 @@ +@model CatherineLynwood.Models.Reviews + +@{ + ViewData["Title"] = "The Alpha Flame: A Gritty 1980s Birmingham Crime Novel about Twin Sisters"; + bool showReviews = Model.Items.Any(); +} + +
+
+ +
+
+ + +
+
+ +
+
+ +
+

The Alpha Flame: Discovery

+

Maggie stands outside the derelict Rubery Hill Hospital. 1983 Birmingham. A story of survival, sisters, and fire.

+
+
+
+ + +
+
+
+ +
+ + +
+ + +
+
+
Formats & delivery
+
    +
  • Hardback, collector’s edition
  • +
  • Paperback, bookshop edition
  • +
  • Fast fulfilment in GB and US via our print partner
  • +
  • Also available from Amazon, Waterstones, Barnes & Noble
  • +
+
+
+ + +
+
+

Buy the Book

+ Best options for your country +
+ + +
+
+ Best price + Direct via print partner +
+ +
+ + + + + + + + +
+ Prefer Kindle? Buy the eBook +
+ + +
+ +
+
+ + +
+
+ The Alpha Flame: Discovery + Buy now +
+
+
+
+
+ + +@if (showReviews) +{ + var top = Model.Items.First(); + var fullStars = (int)Math.Floor(top.RatingValue); + var hasHalfStar = top.RatingValue - fullStars >= 0.5; + var emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0); + var reviewDate = top.DatePublished.ToString("d MMMM yyyy"); + +
+
+
+

★ Reader Praise ★

+
+ + @for (int i = 0; i < fullStars; i++) + { + + } + @if (hasHalfStar) + { + + } + @for (int i = 0; i < emptyStars; i++) + { + + } + + @Html.Raw(top.ReviewBody) +
+ @top.AuthorName on + + @if (string.IsNullOrEmpty(top.URL)) + { + @top.SiteName + } + else + { + + @top.SiteName + } + + , @reviewDate +
+
+ @if (Model.Items.Count > 1) + { + + } +
+
+
+} + + +
+
+
+

The Alpha Flame: Discovery

+

Survival, secrets, and sisters in 1983 Birmingham.

+
+
+ +
+
+ +
+
+
+ +
+

Listen to Catherine talking about the book

+
+
+ + +

+ Set in 1983 Birmingham, The Alpha Flame: Discovery follows twin sisters Beth and Maggie as they uncover dark family secrets and fight to survive. Gritty and emotionally charged, it explores the bond between two women who refuse to be broken. +

+ + +

+ +

+ + +
+
+

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.

+

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.

+

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.

+

Set against the kaleidoscope of 1983, where synthesised anthems provide a pulsing soundtrack and the streets are alive with the bold styles and rebellious energy of the decade, their story unfolds in a city teeming with danger and intrigue. From high-speed chases along winding roads to dimly lit clubs and desolate alleyways, the heroines’ journey is a visceral exploration of survival and freedom. The neon haze of the era contrasts sharply with the stark realities they face, painting a vivid picture of a world where strength and vulnerability coexist.

+

As secrets surface and danger tightens its grip, Beth and Maggie must confront not only the challenges around them but the truths within themselves. Their bond is tested by betrayal, desire, and the shadows of their pasts, but through it all, their flame burns brighter, illuminating their courage and the unbreakable spirit of two heroines determined to rewrite their fates.

+

At its heart, The Alpha Flame is a story of survival, passion, and empowerment. It captures the raw power of human connection against the gritty, vibrant backdrop of an unforgettable era. With its blend of drama, intensity, and unapologetic emotion, this is a story that will leave its mark long after the final page.

+
+
+
+
+
+ + +
+

Chapter Previews

+
+
+
+ + + +
+

Chapter 1: Drowning in Silence — Beth

+

Beth returns home to find her mother lifeless in the bath...

+ +
+
+
+
+
+ + + +
+

Chapter 2: The Last Lesson — Maggie

+

On Christmas Eve, Maggie nervously heads out for her driving test...

+ +
+
+
+
+
+ + + +
+

Chapter 13: A Name She Never Owned — Susie

+

Susie goes out for a drink with a punter. What on earth could go wrong...

+ +
+
+
+
+
+ +@section Scripts { + + + + + + + + +} + +@section Meta { + + + +} diff --git a/CatherineLynwood/Views/Discovery/Soundtrack.cshtml b/CatherineLynwood/Views/Discovery/Soundtrack.cshtml new file mode 100644 index 0000000..6b12c5c --- /dev/null +++ b/CatherineLynwood/Views/Discovery/Soundtrack.cshtml @@ -0,0 +1,198 @@ +@model List +@{ + ViewData["Title"] = "Alpha Flame • Soundtrack"; +} + +
+
+

The Alpha Flame • Soundtrack

+

Eight original tracks inspired by key chapters; listen while you read…

+
+ +
+ @if (Model != null && Model.Any()) + { + var index = 0; + foreach (var track in Model) + { + var id = $"track-{index++}"; +
+
+
+ +
+
+ + +
+
+ + +
+
+

@track.Title

+ @if (!string.IsNullOrWhiteSpace(track.Chapter) || !string.IsNullOrWhiteSpace(track.Description)) + { +

+ @if (!string.IsNullOrWhiteSpace(track.Chapter)) + { + Chapter: @track.Chapter + } + @if (!string.IsNullOrWhiteSpace(track.Chapter) && !string.IsNullOrWhiteSpace(track.Description)) + { + + } + @if (!string.IsNullOrWhiteSpace(track.Description)) + { + @track.Description + } +

+ } + +
+ @if (!string.IsNullOrWhiteSpace(track.LyricsHtml)) + { + @Html.Raw(track.LyricsHtml) + } +
+ +
+ + +
+ + + +
+
+
+
+
+ } + } + else + { +
+
+ Tracks will appear here soon. +
+
+ } +
+ + +
+ + + +@section Scripts { + +} diff --git a/CatherineLynwood/Views/Discovery/Trailer.cshtml b/CatherineLynwood/Views/Discovery/Trailer.cshtml index b3782a4..2bfac49 100644 --- a/CatherineLynwood/Views/Discovery/Trailer.cshtml +++ b/CatherineLynwood/Views/Discovery/Trailer.cshtml @@ -11,7 +11,7 @@

The Alpha Flame: Discovery

-

A gritty Birmingham crime novel set in 1983

+

A gritty Birmingham crime novel set in 1983

@@ -27,17 +27,39 @@ +@if (DateTime.Now < new DateTime(2025, 8, 21)) +{ +
+
+
+

Released in:

+
+ 0d + 00h + 00m + 00s +
+ + + +
+
+
+} + +
-

Show your support

+

Please show your support

Tap your flag to show your support.

@foreach (var kv in Model.FlagCounts) { + string pulse = ""; var code = kv.Key; var count = kv.Value; var name = code switch @@ -55,7 +77,12 @@ "uk" => "gb", _ => code.ToLower() }; -
- +
+
+
+

Listen to The Alpha Flame theme tune
The Flame We Found

+ +
+
+
@@ -92,12 +128,61 @@

A glimpse inside

+
+
+
+
+
+

+ The Alpha Flame: Discovery +

+

+ Some girls survive. Others set the world on fire. +

+

+ She didn’t go looking for trouble. But when she found Beth, bruised, broken, and terrified, Maggie couldn’t walk away. +

+

+ But nothing prepares her for Beth. +

+

+ As she digs deeper into Beth’s world, Maggie finds herself pulled into the shadows, a seedy underworld of secrets, survival, and control, where loyalty is rare and nothing is guaranteed. The more she uncovers, the more she realises this isn’t someone else’s nightmare. It’s her own. +

+

+ The Alpha Flame: Discovery is a gritty, emotionally charged thriller that pulls no punches. Raw, real, and anything but a fairy tale, it’s a story of survival, sisterhood, and fire. +

+
+ +
+
+
+ +
+
+
-

“You looking for something, love?” she asked, her voice soft but direct. Her lips were parted just slightly, her breath misting against the cold window.

+

+ I eased the TR6 down a side street, the headlights sweeping over a figure shifting in the shadows. A movement to my left. A woman, young, her face pale beneath the heavy makeup, stepped forward as I slowed at the junction. She leaned down to my passenger window, so close I could see the faint smudge of lipstick at the corner of her mouth. +

+

+ A loud knock on the glass made me jump. +

+

+ “You looking for something, love?” she asked, her voice soft but direct. Her lips were parted just slightly, her breath misting against the cold window. +

+

+ My stomach tightened. +

+

+ I wasn’t looking for anything. Not really. But I didn’t drive away either. +

+

+ She was close now, close enough that I could see the dark liner smudged beneath her eyes, the glint of something unreadable in her gaze. Not quite curiosity. Not quite suspicion. Just a quiet knowing. +

@@ -111,7 +196,30 @@
-

“Maggie…” My voice broke. “It’s hers. She used to wear this all the time. She was wearing it the last time I saw her.”

+

+ “Maggie… wait.” +

+

+ She turned as I crouched down. My stomach dropped. +

+

+ It was a sweatshirt. Pink. Faded. Cartoon print on the front, cracked with age and wear. Garfield, grinning. +

+

+ I reached out slowly, fingertips brushing the fabric. The left sleeve was soaked, stiff with something dark. +

+

+ Blood. +

+

+ “Maggie…” My voice broke. “It’s hers. She used to wear this all the time. She was wearing it the last time I saw her.” +

+

+ Maggie dropped to her knees beside me, torch trembling in her grip. “Bloody hell. You’re right.” +

+

+ For a second neither of us moved. The building suddenly felt tighter, like it was watching us. +

@@ -121,12 +229,30 @@
-
+
-

Waves erased our footprints; morning would come. So would he.

+

+ She turned in the water, soaked to the waist, flinging droplets everywhere. +

+

+ “Maggie! Come on!” she shouted, laughing. “You’ve got to feel this!” +

+

+ I didn’t hesitate. +

+

+ I peeled off my hoody and shorts, left them in a heap on the rocks, and sprinted after her, my bikini clinging tight to my skin in the salty breeze. The sand stung slightly as I ran, then came the cold slap of the sea, wrapping around my legs and dragging a breathless laugh out of me. +

+

+ Beth was already dancing through the waves like a lunatic. +

+

+ We collided mid-splash, both of us soaked, screaming and laughing like we were eight years old again, like we’d somehow got all those childhood summers back in one moment. + The sea was freezing, but we didn’t care. +

@@ -150,11 +276,15 @@ - -@* +@* + + *@ @section Scripts { + +@if (DateTime.Now < new DateTime(2025, 8, 21)) + { + + } } diff --git a/CatherineLynwood/Views/Shared/_Layout.cshtml b/CatherineLynwood/Views/Shared/_Layout.cshtml index 2e6b184..04f3955 100644 --- a/CatherineLynwood/Views/Shared/_Layout.cshtml +++ b/CatherineLynwood/Views/Shared/_Layout.cshtml @@ -13,9 +13,12 @@