Save
This commit is contained in:
parent
4759fbbd69
commit
3ce32496e8
@ -1,11 +1,10 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Security.Cryptography;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Text;
|
||||||
using CatherineLynwood.Models;
|
using CatherineLynwood.Models;
|
||||||
using CatherineLynwood.Services;
|
using CatherineLynwood.Services;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace CatherineLynwood.Controllers
|
namespace CatherineLynwood.Controllers
|
||||||
@ -16,31 +15,40 @@ namespace CatherineLynwood.Controllers
|
|||||||
private readonly DataAccess _dataAccess;
|
private readonly DataAccess _dataAccess;
|
||||||
private readonly ICountryContext _country;
|
private readonly ICountryContext _country;
|
||||||
|
|
||||||
|
// A/B/C constants
|
||||||
|
private const string VariantCookie = "af_disc_abc";
|
||||||
|
private const string VariantQuery = "ab"; // ?ab=A or B or C
|
||||||
|
|
||||||
public DiscoveryController(DataAccess dataAccess, ICountryContext country)
|
public DiscoveryController(DataAccess dataAccess, ICountryContext country)
|
||||||
{
|
{
|
||||||
_dataAccess = dataAccess;
|
_dataAccess = dataAccess;
|
||||||
_country = country;
|
_country = country;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
// Pages
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
[Route("")]
|
[Route("")]
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
{
|
{
|
||||||
// 1) Resolve country ISO2 (already set by middleware)
|
// Prevent caches or CDNs from cross-serving variants
|
||||||
var iso2 = (_country.Iso2 ?? "GB").ToUpperInvariant();
|
Response.Headers["Vary"] = "Cookie, User-Agent";
|
||||||
if (iso2 == "UK") iso2 = "GB"; // normalise
|
|
||||||
|
|
||||||
// 2) Build server-side link slugs for this user
|
// Decide device class first
|
||||||
|
var device = ResolveDeviceClass();
|
||||||
|
|
||||||
|
// Decide variant
|
||||||
|
var variant = ResolveVariant(device);
|
||||||
|
|
||||||
|
// Country ISO2
|
||||||
|
var iso2 = (_country.Iso2 ?? "GB").ToUpperInvariant();
|
||||||
|
if (iso2 == "UK") iso2 = "GB";
|
||||||
|
|
||||||
|
// Buy links
|
||||||
var buyLinks = BuildBuyLinksFor(iso2);
|
var buyLinks = BuildBuyLinksFor(iso2);
|
||||||
|
|
||||||
// 3) Load reviews (unchanged)
|
// Reviews
|
||||||
Reviews reviews = await _dataAccess.GetReviewsAsync();
|
Reviews reviews = await _dataAccess.GetReviewsAsync();
|
||||||
reviews.SchemaJsonLd = GenerateBookSchemaJsonLd(reviews, 3);
|
reviews.SchemaJsonLd = GenerateBookSchemaJsonLd(reviews, 3);
|
||||||
|
|
||||||
// 4) Compose page VM
|
// VM
|
||||||
var vm = new DiscoveryPageViewModel
|
var vm = new DiscoveryPageViewModel
|
||||||
{
|
{
|
||||||
Reviews = reviews,
|
Reviews = reviews,
|
||||||
@ -48,8 +56,17 @@ namespace CatherineLynwood.Controllers
|
|||||||
Buy = buyLinks
|
Buy = buyLinks
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use your existing view (rename if you prefer): Index1.cshtml
|
// View mapping:
|
||||||
return View("Index1", vm);
|
// - A and B are the two MOBILE variants
|
||||||
|
// - C is the DESKTOP variant
|
||||||
|
string viewName = variant switch
|
||||||
|
{
|
||||||
|
Variant.A => "IndexMobileA", // mobile layout A
|
||||||
|
Variant.B => "IndexMobileB", // mobile layout B
|
||||||
|
_ => "IndexDesktop" // desktop layout C
|
||||||
|
};
|
||||||
|
|
||||||
|
return View(viewName, vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route("reviews")]
|
[Route("reviews")]
|
||||||
@ -57,7 +74,6 @@ namespace CatherineLynwood.Controllers
|
|||||||
{
|
{
|
||||||
Reviews reviews = await _dataAccess.GetReviewsAsync();
|
Reviews reviews = await _dataAccess.GetReviewsAsync();
|
||||||
reviews.SchemaJsonLd = GenerateBookSchemaJsonLd(reviews, 100);
|
reviews.SchemaJsonLd = GenerateBookSchemaJsonLd(reviews, 100);
|
||||||
// If you prefer a strongly-typed page VM here too, you can wrap, but returning Reviews keeps your existing view working
|
|
||||||
return View(reviews);
|
return View(reviews);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,9 +117,146 @@ namespace CatherineLynwood.Controllers
|
|||||||
return View(soundtrackTrackModels);
|
return View(soundtrackTrackModels);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------
|
// =========================
|
||||||
// Private helpers
|
// A/B/C selection
|
||||||
// -------------------------
|
// =========================
|
||||||
|
|
||||||
|
private enum Variant { A, B, C }
|
||||||
|
private enum DeviceClass { Mobile, Desktop }
|
||||||
|
|
||||||
|
private Variant ResolveVariant(DeviceClass device)
|
||||||
|
{
|
||||||
|
// 0) Query override: ?ab=A|B|C forces and persists
|
||||||
|
if (Request.Query.TryGetValue(VariantQuery, out var qs))
|
||||||
|
{
|
||||||
|
var parsed = ParseVariant(qs.ToString());
|
||||||
|
if (parsed.HasValue)
|
||||||
|
{
|
||||||
|
// If they force A or B but device is desktop, we still respect C logic by default.
|
||||||
|
// Allow override to win for testing convenience.
|
||||||
|
WriteVariantCookie(parsed.Value);
|
||||||
|
return parsed.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1) Existing cookie
|
||||||
|
if (Request.Cookies.TryGetValue(VariantCookie, out var cookieVal))
|
||||||
|
{
|
||||||
|
var parsed = ParseVariant(cookieVal);
|
||||||
|
if (parsed.HasValue)
|
||||||
|
{
|
||||||
|
// If cookie says A/B but device is desktop, upgrade to C to keep desktop experience consistent.
|
||||||
|
if (device == DeviceClass.Desktop && parsed.Value != Variant.C)
|
||||||
|
{
|
||||||
|
WriteVariantCookie(Variant.C);
|
||||||
|
return Variant.C;
|
||||||
|
}
|
||||||
|
// If cookie says C but device is mobile, you can either keep C or reassign to A/B.
|
||||||
|
// We keep C only for desktops. On mobile we reassess to A/B the first time after cookie mismatch.
|
||||||
|
if (device == DeviceClass.Mobile && parsed.Value == Variant.C)
|
||||||
|
{
|
||||||
|
var reassigned = AssignMobileVariant();
|
||||||
|
WriteVariantCookie(reassigned);
|
||||||
|
return reassigned;
|
||||||
|
}
|
||||||
|
return parsed.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Fresh assignment
|
||||||
|
if (device == DeviceClass.Desktop)
|
||||||
|
{
|
||||||
|
WriteVariantCookie(Variant.C);
|
||||||
|
return Variant.C;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var assigned = AssignMobileVariant(); // A or B
|
||||||
|
WriteVariantCookie(assigned);
|
||||||
|
return assigned;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Variant AssignMobileVariant(int bPercent = 50)
|
||||||
|
{
|
||||||
|
bPercent = Math.Clamp(bPercent, 0, 100);
|
||||||
|
var roll = RandomNumberGenerator.GetInt32(0, 100); // 0..99
|
||||||
|
return roll < bPercent ? Variant.B : Variant.A;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Variant? ParseVariant(string value)
|
||||||
|
{
|
||||||
|
if (string.Equals(value, "A", StringComparison.OrdinalIgnoreCase)) return Variant.A;
|
||||||
|
if (string.Equals(value, "B", StringComparison.OrdinalIgnoreCase)) return Variant.B;
|
||||||
|
if (string.Equals(value, "C", StringComparison.OrdinalIgnoreCase)) return Variant.C;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteVariantCookie(Variant variant)
|
||||||
|
{
|
||||||
|
var opts = new CookieOptions
|
||||||
|
{
|
||||||
|
Expires = DateTimeOffset.UtcNow.AddDays(90),
|
||||||
|
HttpOnly = false, // set true if you do not need analytics to read it
|
||||||
|
Secure = true,
|
||||||
|
SameSite = SameSiteMode.Lax,
|
||||||
|
Path = "/"
|
||||||
|
};
|
||||||
|
Response.Cookies.Append(VariantCookie, variant.ToString(), opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// Device detection
|
||||||
|
// =========================
|
||||||
|
private DeviceClass ResolveDeviceClass()
|
||||||
|
{
|
||||||
|
// Simple and robust server-side approach using User-Agent.
|
||||||
|
// Chrome UA reduction still leaves Mobile hint for Android; iOS strings include iPhone/iPad.
|
||||||
|
var ua = Request.Headers.UserAgent.ToString();
|
||||||
|
|
||||||
|
if (IsMobileUserAgent(ua))
|
||||||
|
return DeviceClass.Mobile;
|
||||||
|
|
||||||
|
return DeviceClass.Desktop;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsMobileUserAgent(string ua)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(ua)) return false;
|
||||||
|
|
||||||
|
// Common mobile indicators
|
||||||
|
// Android phones include "Android" and "Mobile"
|
||||||
|
// iPhone includes "iPhone"; iPad includes "iPad" (treat as mobile for your layouts)
|
||||||
|
// Many mobile browsers include "Mobile"
|
||||||
|
// Exclude obvious desktop platforms
|
||||||
|
ua = ua.ToLowerInvariant();
|
||||||
|
|
||||||
|
if (ua.Contains("ipad") || ua.Contains("iphone") || ua.Contains("ipod"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (ua.Contains("android") && ua.Contains("mobile"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (ua.Contains("android") && !ua.Contains("mobile"))
|
||||||
|
{
|
||||||
|
// Likely a tablet; treat as mobile for your use case
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ua.Contains("mobile"))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Desktop hints
|
||||||
|
if (ua.Contains("windows nt") || ua.Contains("macintosh") || ua.Contains("x11"))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Fallback
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// Existing helpers
|
||||||
|
// =========================
|
||||||
|
|
||||||
private static BuyLinksViewModel BuildBuyLinksFor(string iso2)
|
private static BuyLinksViewModel BuildBuyLinksFor(string iso2)
|
||||||
{
|
{
|
||||||
@ -149,7 +302,7 @@ namespace CatherineLynwood.Controllers
|
|||||||
|
|
||||||
LinkChoice? mk(string? slug) => string.IsNullOrEmpty(slug) ? null : new LinkChoice { Slug = slug, Url = BuyCatalog.Url(slug) };
|
LinkChoice? mk(string? slug) => string.IsNullOrEmpty(slug) ? null : new LinkChoice { Slug = slug, Url = BuyCatalog.Url(slug) };
|
||||||
|
|
||||||
return new BuyLinksViewModel
|
var vm = new BuyLinksViewModel
|
||||||
{
|
{
|
||||||
Country = cc,
|
Country = cc,
|
||||||
IngramHardback = mk(ingHbSlug),
|
IngramHardback = mk(ingHbSlug),
|
||||||
@ -163,8 +316,20 @@ namespace CatherineLynwood.Controllers
|
|||||||
Apple = mk(appleSlug)!,
|
Apple = mk(appleSlug)!,
|
||||||
Kobo = mk(koboSlug)!
|
Kobo = mk(koboSlug)!
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (cc == "GB")
|
||||||
|
{
|
||||||
|
vm.IngramHardbackPrice = "£16.99";
|
||||||
|
vm.IngramPaperbackPrice = "£12.99";
|
||||||
|
}
|
||||||
|
else if (cc == "US")
|
||||||
|
{
|
||||||
|
vm.IngramHardbackPrice = "$24.99";
|
||||||
|
vm.IngramPaperbackPrice = "$16.99";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
private string GenerateBookSchemaJsonLd(Reviews reviews, int take)
|
private string GenerateBookSchemaJsonLd(Reviews reviews, int take)
|
||||||
{
|
{
|
||||||
@ -190,13 +355,12 @@ namespace CatherineLynwood.Controllers
|
|||||||
["name"] = "Catherine Lynwood"
|
["name"] = "Catherine Lynwood"
|
||||||
},
|
},
|
||||||
["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...",
|
["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...",
|
||||||
["genre"] = "Women's Fiction, Mystery, Contemporary Historical",
|
["genre"] = "Women's Fiction, Mystery, Contemporary Historical",
|
||||||
["inLanguage"] = "en-GB",
|
["inLanguage"] = "en-GB",
|
||||||
["url"] = baseUrl
|
["url"] = baseUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reviews
|
|
||||||
if (reviews?.Items?.Any() == true)
|
if (reviews?.Items?.Any() == true)
|
||||||
{
|
{
|
||||||
var reviewObjects = new List<Dictionary<string, object>>();
|
var reviewObjects = new List<Dictionary<string, object>>();
|
||||||
@ -233,7 +397,6 @@ namespace CatherineLynwood.Controllers
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Work examples
|
|
||||||
schema["workExample"] = new List<Dictionary<string, object>>
|
schema["workExample"] = new List<Dictionary<string, object>>
|
||||||
{
|
{
|
||||||
new Dictionary<string, object>
|
new Dictionary<string, object>
|
||||||
|
|||||||
12
CatherineLynwood/Models/AbTestOptions.cs
Normal file
12
CatherineLynwood/Models/AbTestOptions.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace CatherineLynwood.Models
|
||||||
|
{
|
||||||
|
public sealed class AbTestOptions
|
||||||
|
{
|
||||||
|
#region Public Properties
|
||||||
|
|
||||||
|
// Percentage of users who should see variant B; the rest see A
|
||||||
|
public int DiscoveryBPercent { get; set; } = 50;
|
||||||
|
|
||||||
|
#endregion Public Properties
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,26 +4,26 @@
|
|||||||
{
|
{
|
||||||
#region Public Properties
|
#region Public Properties
|
||||||
|
|
||||||
// Amazon (always)
|
|
||||||
public LinkChoice AmazonHardback { get; set; } = new();
|
public LinkChoice AmazonHardback { get; set; } = new();
|
||||||
|
|
||||||
public LinkChoice AmazonKindle { get; set; } = new();
|
public LinkChoice AmazonKindle { get; set; } = new();
|
||||||
|
|
||||||
public LinkChoice AmazonPaperback { get; set; } = new();
|
public LinkChoice AmazonPaperback { get; set; } = new();
|
||||||
|
|
||||||
// eBooks (always)
|
|
||||||
public LinkChoice Apple { get; set; } = new();
|
public LinkChoice Apple { get; set; } = new();
|
||||||
|
|
||||||
public string Country { get; set; } = "GB";
|
public string Country { get; set; } = "GB";
|
||||||
|
|
||||||
// Direct (optional)
|
|
||||||
public LinkChoice? IngramHardback { get; set; }
|
public LinkChoice? IngramHardback { get; set; }
|
||||||
|
|
||||||
|
public string? IngramHardbackPrice { get; set; }
|
||||||
|
|
||||||
public LinkChoice? IngramPaperback { get; set; }
|
public LinkChoice? IngramPaperback { get; set; }
|
||||||
|
|
||||||
|
public string? IngramPaperbackPrice { get; set; }
|
||||||
|
|
||||||
public LinkChoice Kobo { get; set; } = new();
|
public LinkChoice Kobo { get; set; } = new();
|
||||||
|
|
||||||
// National (optional)
|
|
||||||
public LinkChoice? NationalHardback { get; set; }
|
public LinkChoice? NationalHardback { get; set; }
|
||||||
|
|
||||||
public string? NationalLabel { get; set; }
|
public string? NationalLabel { get; set; }
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using CatherineLynwood.Middleware;
|
using CatherineLynwood.Middleware;
|
||||||
|
using CatherineLynwood.Models;
|
||||||
using CatherineLynwood.Services;
|
using CatherineLynwood.Services;
|
||||||
|
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
@ -83,6 +84,8 @@ namespace CatherineLynwood
|
|||||||
});
|
});
|
||||||
builder.Services.Configure<BrotliCompressionProviderOptions>(o => o.Level = CompressionLevel.Fastest);
|
builder.Services.Configure<BrotliCompressionProviderOptions>(o => o.Level = CompressionLevel.Fastest);
|
||||||
builder.Services.Configure<GzipCompressionProviderOptions>(o => o.Level = CompressionLevel.Fastest);
|
builder.Services.Configure<GzipCompressionProviderOptions>(o => o.Level = CompressionLevel.Fastest);
|
||||||
|
builder.Services.Configure<AbTestOptions>(builder.Configuration.GetSection("AbTest"));
|
||||||
|
|
||||||
|
|
||||||
// HTML minification
|
// HTML minification
|
||||||
builder.Services.AddWebMarkupMin(o =>
|
builder.Services.AddWebMarkupMin(o =>
|
||||||
|
|||||||
@ -1,383 +0,0 @@
|
|||||||
@model CatherineLynwood.Models.Reviews
|
|
||||||
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "The Alpha Flame: A Gritty 1980s Birmingham Crime Novel about Twin Sisters";
|
|
||||||
|
|
||||||
bool showReviews = Model.Items.Any();
|
|
||||||
}
|
|
||||||
|
|
||||||
<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">Discovery</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Specific Row for Book Cover and Synopsis -->
|
|
||||||
<div class="row">
|
|
||||||
<!-- Book Cover Section -->
|
|
||||||
<div class="col-md-4">
|
|
||||||
<section id="book-cover">
|
|
||||||
<div class="card character-card" id="cover-card">
|
|
||||||
<responsive-image src="the-alpha-flame-discovery-cover.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">
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (showReviews)
|
|
||||||
{
|
|
||||||
<!-- Buy Section -->
|
|
||||||
<div class="col-md-8">
|
|
||||||
<section id="purchase-and-reviews">
|
|
||||||
<div class="card character-card" id="companion-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<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 class="card-body" id="companion-body">
|
|
||||||
<!-- Buy Section -->
|
|
||||||
<div id="buy-now" class="mb-4">
|
|
||||||
<a asp-action="HowToBuy" class="btn btn-dark">
|
|
||||||
<i class="fad fa-books"> </i> Buy the Book
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Reader Reviews -->
|
|
||||||
<div class="reader-reviews">
|
|
||||||
<h3 class="h6 text-uppercase text-muted">★ Reader Praise ★</h3>
|
|
||||||
|
|
||||||
@foreach (var review in Model.Items.Take(3))
|
|
||||||
{
|
|
||||||
var fullStars = (int)Math.Floor(review.RatingValue);
|
|
||||||
var hasHalfStar = review.RatingValue - fullStars >= 0.5;
|
|
||||||
var emptyStars = 5 - fullStars - (hasHalfStar ? 1 : 0);
|
|
||||||
var reviewDate = review.DatePublished.ToString("d MMMM yyyy");
|
|
||||||
|
|
||||||
<blockquote class="blockquote mb-4">
|
|
||||||
<span class="mb-2 text-warning">
|
|
||||||
@for (int i = 0; i < fullStars; i++)
|
|
||||||
{
|
|
||||||
<i class="fad fa-star"></i>
|
|
||||||
}
|
|
||||||
@if (hasHalfStar)
|
|
||||||
{
|
|
||||||
<i class="fad fa-star-half-alt"></i>
|
|
||||||
}
|
|
||||||
@for (int i = 0; i < emptyStars; i++)
|
|
||||||
{
|
|
||||||
<i class="fad fa-star" style="--fa-primary-opacity: 0.2; --fa-secondary-opacity: 0.2;"></i>
|
|
||||||
}
|
|
||||||
</span>
|
|
||||||
@Html.Raw(review.ReviewBody)
|
|
||||||
<footer>
|
|
||||||
@review.AuthorName on <cite title="@review.SiteName">
|
|
||||||
@if (string.IsNullOrEmpty(review.URL))
|
|
||||||
{
|
|
||||||
@review.SiteName
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<a href="@review.URL" target="_blank">@review.SiteName</a>
|
|
||||||
}
|
|
||||||
</cite> — <span class="text-muted smaller">@reviewDate</span>
|
|
||||||
</footer>
|
|
||||||
</blockquote>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (Model.Items.Count > 3)
|
|
||||||
{
|
|
||||||
<div class="text-end">
|
|
||||||
<a asp-action="Reviews" class="btn btn-outline-secondary btn-sm">
|
|
||||||
Read More Reviews
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Synopsis Section -->
|
|
||||||
<div class="col-md-12">
|
|
||||||
<section id="synopsis">
|
|
||||||
<div class="card character-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h2 class="card-title h1">The Alpha Flame: <span class="fw-light">Discovery:</span> Synopsis</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-body" id="synopsis-body">
|
|
||||||
<div class="row align-items-center">
|
|
||||||
<div class="col-2">
|
|
||||||
<responsive-image src="catherine-lynwood-16.png" class="img-fluid rounded-circle border border-2 border-dark" alt="Catherine Lynwood" display-width-percentage="20"></responsive-image>
|
|
||||||
</div>
|
|
||||||
<div class="col-10">
|
|
||||||
<!-- Audio Section -->
|
|
||||||
<div class="audio-player text-center">
|
|
||||||
<audio id="player">
|
|
||||||
<source src="/audio/the-alpha-flame-discovery-synopsis.mp3" type="audio/mpeg">
|
|
||||||
Your browser does not support the audio element.
|
|
||||||
</audio>
|
|
||||||
</div>
|
|
||||||
<p class="text-center text-muted small">
|
|
||||||
Listen to Catherine telling you about The Alpha Flame: Discovery
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Synopsis Content -->
|
|
||||||
<h3 class="card-title">Synopsis</h3>
|
|
||||||
<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">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">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.</p>
|
|
||||||
<p class="card-text">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.</p>
|
|
||||||
<p class="card-text">At its heart, The Alpha Flame is a story of survival, passion, and empowerment. It explores the devastating lows and triumphant highs of life with unflinching honesty, capturing 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 frame.</p>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<!-- Synopsis Section -->
|
|
||||||
<div class="col-md-8">
|
|
||||||
<section id="synopsis">
|
|
||||||
<div class="card character-card" id="companion-card">
|
|
||||||
<div class="card-header">
|
|
||||||
<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 class="card-body" id="companion-body">
|
|
||||||
<div class="row align-items-center">
|
|
||||||
<div class="col-2">
|
|
||||||
<responsive-image src="catherine-lynwood-16.png" class="img-fluid rounded-circle border border-2 border-dark" alt="Catherine Lynwood" display-width-percentage="20"></responsive-image>
|
|
||||||
</div>
|
|
||||||
<div class="col-10">
|
|
||||||
<!-- Audio Section -->
|
|
||||||
<div class="audio-player text-center">
|
|
||||||
<audio id="player">
|
|
||||||
<source src="/audio/the-alpha-flame-discovery-synopsis.mp3" type="audio/mpeg">
|
|
||||||
Your browser does not support the audio element.
|
|
||||||
</audio>
|
|
||||||
</div>
|
|
||||||
<p class="text-center text-muted small">
|
|
||||||
Listen to Catherine telling you about The Alpha Flame: Discovery
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<div id="buy-now" class="my-4">
|
|
||||||
<a id="kindleLink" href="https://www.amazon.co.uk/dp/B0FBS427VD" target="_blank" class="btn btn-dark mb-2">
|
|
||||||
Buy Kindle Edition
|
|
||||||
</a>
|
|
||||||
<a id="paperbackLink" href="https://www.amazon.co.uk/dp/1068225815" target="_blank" class="btn btn-dark mb-2">
|
|
||||||
Buy Paperback (Bookshop Edition)
|
|
||||||
</a>
|
|
||||||
<a id="hardbackLink" href="https://www.amazon.co.uk/dp/1068225807" target="_blank" class="btn btn-dark mb-2">
|
|
||||||
Buy Hardback (Collector's Edition)
|
|
||||||
</a>
|
|
||||||
<p id="geoNote" class="text-muted small mt-2">
|
|
||||||
Available from your local Amazon store.<br />
|
|
||||||
Or order from your local bookshop using:
|
|
||||||
<ul class="small text-muted">
|
|
||||||
<li>
|
|
||||||
ISBN 978-1-0682258-1-9 - Bookshop Edition (Paperback)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
ISBN 978-1-0682258-0-2 - Collector's Eidtion (Hardback)
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<span id="extraRetailers"></span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Synopsis Content -->
|
|
||||||
<h3 class="card-title">Synopsis</h3>
|
|
||||||
<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">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">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.</p>
|
|
||||||
<p class="card-text">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.</p>
|
|
||||||
<p class="card-text">At its heart, The Alpha Flame is a story of survival, passion, and empowerment. It explores the devastating lows and triumphant highs of life with unflinching honesty, capturing 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 frame.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Giveway Section -->
|
|
||||||
@if (DateTime.Now < new DateTime(2025, 9, 1))
|
|
||||||
{
|
|
||||||
<div class="col-md-12 mt-4">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<h2 class="display-6 fw-bold">Win: <span class="fw-light">a Collector’s Edition of The Alpha Flame: Discovery</span></h2>
|
|
||||||
<p class="mb-2">
|
|
||||||
Enter my giveaway for your chance to own this special edition. Signed in the UK, or delivered as a premium collector’s copy worldwide.
|
|
||||||
</p>
|
|
||||||
<em>Exclusive, limited, beautiful.</em>
|
|
||||||
<div class="row justify-content-center">
|
|
||||||
<div class="col-8 col-md-4 mt-4">
|
|
||||||
<a asp-controller="TheAlphaFlame" asp-action="Giveaways" class="btn btn-dark">Enter now for your chance.</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Chapter Previews Section -->
|
|
||||||
<section id="chapters" class="mt-4">
|
|
||||||
<h2>Chapter Previews</h2>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-4 mb-4">
|
|
||||||
<div class="card h-100 character-card">
|
|
||||||
<a asp-action="Chapter1">
|
|
||||||
<responsive-image src="beth-stood-in-bathroom.png" class="card-img-top" alt="Beth's Bathroom" 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...</p>
|
|
||||||
<div class="text-end"><a asp-action="Chapter1" class="btn btn-dark">Read More</a></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-4">
|
|
||||||
<div class="card h-100 character-card">
|
|
||||||
<a asp-action="Chapter2">
|
|
||||||
<responsive-image src="maggie-with-her-tr6-2.png" class="fit-image" alt="Maggie With Her TR6" 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...</p>
|
|
||||||
<div class="text-end"><a asp-action="Chapter2" class="btn btn-dark">Read More</a></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4 mb-4">
|
|
||||||
<div class="card h-100 character-card">
|
|
||||||
<a asp-action="Chapter13">
|
|
||||||
<responsive-image src="pub-from-chapter-13.png" class="fit-image" alt="Pub from Chapter 13" display-width-percentage="50"></responsive-image>
|
|
||||||
</a>
|
|
||||||
<div class="card-body border-top border-3 border-dark">
|
|
||||||
<h3 class="card-title">Chapter 13: A Name She Never Owned - Susie</h3>
|
|
||||||
<p class="card-text">Susie goes out for a drink with a punter. What on earth could go wrong...</p>
|
|
||||||
<div class="text-end"><a asp-action="Chapter13" class="btn btn-dark">Read More</a></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
<script>
|
|
||||||
window.addEventListener("load", function () {
|
|
||||||
const coverCard = document.getElementById("cover-card");
|
|
||||||
const companionCard = document.getElementById("companion-card");
|
|
||||||
const companionBody = document.getElementById("companion-body");
|
|
||||||
|
|
||||||
if (coverCard && companionCard && companionBody) {
|
|
||||||
// Match the height of the synopsis card to the cover card
|
|
||||||
const coverHeight = coverCard.offsetHeight;
|
|
||||||
companionCard.style.height = `${coverHeight}px`;
|
|
||||||
|
|
||||||
// Adjust the synopsis body to scroll within the matched height
|
|
||||||
const headerHeight = companionCard.querySelector(".card-header").offsetHeight;
|
|
||||||
companionBody.style.maxHeight = `${coverHeight - headerHeight}px`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
const player = new Plyr('audio');
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
fetch('https://ipapi.co/json/')
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
const country = data.country_code;
|
|
||||||
|
|
||||||
let kindleLink = "https://www.amazon.com/dp/B0FBS427VD";
|
|
||||||
let paperbackLink = "https://www.amazon.com/dp/1068225815";
|
|
||||||
let hardbackLink = "https://www.amazon.com/dp/1068225807";
|
|
||||||
let extraRetailers = "";
|
|
||||||
|
|
||||||
switch (country) {
|
|
||||||
case "GB":
|
|
||||||
kindleLink = "https://www.amazon.co.uk/dp/B0FBS427VD";
|
|
||||||
paperbackLink = "https://www.amazon.co.uk/dp/1068225815";
|
|
||||||
hardbackLink = "https://www.amazon.co.uk/dp/1068225807";
|
|
||||||
extraRetailers = 'Also available at <a href="https://www.waterstones.com/book/the-alpha-flame/catherine-lynwood/9781068225819" target="_blank">Waterstons</a>';
|
|
||||||
break;
|
|
||||||
case "US":
|
|
||||||
kindleLink = "https://www.amazon.com/dp/B0FBS427VD";
|
|
||||||
paperbackLink = "https://www.amazon.com/dp/1068225815";
|
|
||||||
hardbackLink = "https://www.amazon.com/dp/1068225807";
|
|
||||||
extraRetailers = 'Also available at <a href="https://www.barnesandnoble.com/s/9781068225810" target="_blank">Barnes & Noble</a>';
|
|
||||||
break;
|
|
||||||
case "CA":
|
|
||||||
kindleLink = "https://www.amazon.ca/dp/B0FBS427VD";
|
|
||||||
paperbackLink = "https://www.amazon.ca/dp/1068225815";
|
|
||||||
hardbackLink = "https://www.amazon.ca/dp/1068225807";
|
|
||||||
break;
|
|
||||||
case "AU":
|
|
||||||
kindleLink = "https://www.amazon.com.au/dp/B0FBS427VD";
|
|
||||||
paperbackLink = "https://www.amazon.com.au/dp/1068225815";
|
|
||||||
hardbackLink = "https://www.amazon.com.au/dp/1068225807";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("kindleLink").setAttribute("href", kindleLink);
|
|
||||||
document.getElementById("paperbackLink").setAttribute("href", paperbackLink);
|
|
||||||
document.getElementById("hardbackLink").setAttribute("href", hardbackLink);
|
|
||||||
document.getElementById("extraRetailers").innerHTML = extraRetailers;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@section Meta{
|
|
||||||
<MetaTag meta-title="The Alpha Flame: Discovery by Catherine Lynwood"
|
|
||||||
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-author="Catherine Lynwood"
|
|
||||||
meta-url="https://www.catherinelynwood.com/the-alpha-flame/discovery"
|
|
||||||
meta-image="https://www.catherinelynwood.com/images/webp/the-alpha-flame-discovery-cover-1200.webp"
|
|
||||||
meta-image-alt="Maggie from 'The Alpha Flame: Discovery' by Catherine Lynwood"
|
|
||||||
og-site-name="Catherine Lynwood - The Alpha Flame: Discovery"
|
|
||||||
article-published-time="@new DateTime(2024, 11, 20)"
|
|
||||||
article-modified-time="@new DateTime(2025, 06, 07)"
|
|
||||||
twitter-card-type="summary_large_image"
|
|
||||||
twitter-site-handle="@@CathLynwood"
|
|
||||||
twitter-creator-handle="@@CathLynwood" />
|
|
||||||
|
|
||||||
<script type="application/ld+json">
|
|
||||||
@Html.Raw(Model.SchemaJsonLd)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
}
|
|
||||||
338
CatherineLynwood/Views/Discovery/IndexDesktop.cshtml
Normal file
338
CatherineLynwood/Views/Discovery/IndexDesktop.cshtml
Normal file
@ -0,0 +1,338 @@
|
|||||||
|
@model CatherineLynwood.Models.DiscoveryPageViewModel
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "The Alpha Flame: A Gritty 1980s Birmingham Crime Novel about Twin Sisters";
|
||||||
|
bool showReviews = Model.Reviews.Items.Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
<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">Discovery</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- HERO: Cover + Trailer + Buy Box -->
|
||||||
|
<section class="mb-4">
|
||||||
|
<div class="row g-3 align-items-stretch">
|
||||||
|
<!-- Book Cover -->
|
||||||
|
<div class="col-lg-5 d-flex d-none d-lg-block">
|
||||||
|
<div class="card character-card h-100 flex-fill" id="cover-card">
|
||||||
|
<responsive-image src="the-alpha-flame-discovery-cover.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">
|
||||||
|
<h3 class="card-title h5 mb-1">The Alpha Flame: <span class="fw-light">Discovery</span></h3>
|
||||||
|
<p class="card-text mb-0">It's 1983 Birmingham. Maggie, my fiery heroine, standing outside the derelict Rubery Hill Hospital. A story of survival, sisters, and fire.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Trailer + Buy Box -->
|
||||||
|
<div class="col-lg-7 d-flex">
|
||||||
|
<div class="card character-card h-100 flex-fill" id="hero-media-card">
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<!-- Trailer -->
|
||||||
|
<div class="trailer-wrapper mb-3">
|
||||||
|
<!-- Desktop: LANDSCAPE -->
|
||||||
|
<video id="trailerLandscape"
|
||||||
|
class="w-100"
|
||||||
|
playsinline
|
||||||
|
preload="none"
|
||||||
|
poster="/images/webp/the-alpha-flame-discovery-trailer-landscape-1400.webp"
|
||||||
|
controls>
|
||||||
|
<source src="/videos/the-alpha-flame-discovery-trailer-landscape.mp4" type="video/mp4">
|
||||||
|
Sorry, your browser doesn't support embedded video.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
<button id="trailerPlayBtn" class="trailer-play-btn">
|
||||||
|
<i class="fad fa-play"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- If JS is off, hide the custom play button so native controls are used -->
|
||||||
|
<noscript>
|
||||||
|
<style>
|
||||||
|
#trailerPlayBtn {
|
||||||
|
display: none !important
|
||||||
|
}</style>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<!-- Buy Box -->
|
||||||
|
@* buyBox: server-side slugs + <a ping> tracking *@
|
||||||
|
@* Model: CatherineLynwood.Models.DiscoveryPageViewModel *@
|
||||||
|
@{
|
||||||
|
var L = Model.Buy;
|
||||||
|
string pingBase = "/track/click";
|
||||||
|
string countryIso2 = Model.UserIso2 ?? "GB";
|
||||||
|
string flagPathSvg = $"/images/flags/{countryIso2}.svg";
|
||||||
|
string flagPathPng = $"/images/flags/{countryIso2}.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
<partial name="_BuyBox" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Social proof: show one standout review near the top -->
|
||||||
|
@if (showReviews)
|
||||||
|
{
|
||||||
|
var top = Model.Reviews.Items.Where(x => x.RatingValue == 5).OrderByDescending(y => y.DatePublished).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");
|
||||||
|
|
||||||
|
<section class="mb-4">
|
||||||
|
<div class="card character-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="h6 text-uppercase text-muted mb-2">★ Reader Praise ★</h3>
|
||||||
|
<blockquote class="blockquote mb-2">
|
||||||
|
<span class="mb-2 text-warning d-inline-block">
|
||||||
|
@for (int i = 0; i < fullStars; i++)
|
||||||
|
{
|
||||||
|
<i class="fad fa-star"></i>
|
||||||
|
}
|
||||||
|
@if (hasHalfStar)
|
||||||
|
{
|
||||||
|
<i class="fad fa-star-half-alt"></i>
|
||||||
|
}
|
||||||
|
@for (int i = 0; i < emptyStars; i++)
|
||||||
|
{
|
||||||
|
<i class="fad fa-star" style="--fa-primary-opacity:0.2;--fa-secondary-opacity:0.2;"></i>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
@Html.Raw(top.ReviewBody)
|
||||||
|
<footer>
|
||||||
|
@top.AuthorName on
|
||||||
|
<cite title="@top.SiteName">
|
||||||
|
@if (string.IsNullOrEmpty(top.URL))
|
||||||
|
{
|
||||||
|
@top.SiteName
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
<a href="@top.URL" target="_blank">@top.SiteName</a>
|
||||||
|
}
|
||||||
|
</cite>
|
||||||
|
<span class="text-muted smaller">, @reviewDate</span>
|
||||||
|
</footer>
|
||||||
|
</blockquote>
|
||||||
|
@if (Model.Reviews.Items.Count > 1)
|
||||||
|
{
|
||||||
|
<div class="text-end">
|
||||||
|
<a asp-action="Reviews" class="btn btn-outline-secondary btn-sm">Read more reviews</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Synopsis: open on mobile, collapsible on desktop -->
|
||||||
|
<section id="synopsis" class="mb-4">
|
||||||
|
<div class="card character-card text-white" style="background: url('/images/webp/synopsis-background-960.webp'); background-position: center; background-size: cover;">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title h1 mb-0">The Alpha Flame: <span class="fw-light">Discovery</span></h2>
|
||||||
|
<p class="mb-0">Survival, secrets, and sisters in 1983 Birmingham.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body" id="synopsis-body">
|
||||||
|
<!-- Audio blurb -->
|
||||||
|
<div class="row align-items-center mb-3">
|
||||||
|
<div class="col-2">
|
||||||
|
<responsive-image src="catherine-lynwood-16.png" class="img-fluid rounded-circle border border-2 border-dark" alt="Catherine Lynwood" display-width-percentage="20"></responsive-image>
|
||||||
|
</div>
|
||||||
|
<div class="col-10">
|
||||||
|
<div class="audio-player text-center">
|
||||||
|
<audio id="player">
|
||||||
|
<source src="/audio/the-alpha-flame-discovery-catherine.mp3" type="audio/mpeg">
|
||||||
|
Your browser does not support the audio element.
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
<p class="text-center text-white small mb-0">Listen to Catherine talking about the book</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Teaser -->
|
||||||
|
<p class="card-text">
|
||||||
|
Set in 1983 Birmingham, nearby Redditch, and Barmouth in Wales, The Alpha Flame: Discovery follows the lives of two young women, 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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Desktop-only: collapse trigger -->
|
||||||
|
<p class="mb-2 d-none d-md-block">
|
||||||
|
<a class="btn btn-outline-light btn-sm"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
href="#fullSynopsis"
|
||||||
|
role="button"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="fullSynopsis">
|
||||||
|
Read full synopsis
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- One copy of the full synopsis -->
|
||||||
|
<div class="collapse" id="fullSynopsis">
|
||||||
|
<div class="mt-2">
|
||||||
|
<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">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">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.</p>
|
||||||
|
<p class="card-text">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.</p>
|
||||||
|
<p class="card-text">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.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Chapter Previews -->
|
||||||
|
<section id="chapters" class="mt-4">
|
||||||
|
<h2>Chapter Previews</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card h-100 character-card">
|
||||||
|
<a asp-action="Chapter1">
|
||||||
|
<responsive-image src="beth-stood-in-bathroom.png" class="card-img-top" alt="Beth's Bathroom" 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...</p>
|
||||||
|
<div class="text-end"><a asp-action="Chapter1" class="btn btn-dark">Read More</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card h-100 character-card">
|
||||||
|
<a asp-action="Chapter2">
|
||||||
|
<responsive-image src="maggie-with-her-tr6-2.png" class="fit-image" alt="Maggie With Her TR6" 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 not knowing the story that will pan out before her...</p>
|
||||||
|
<div class="text-end"><a asp-action="Chapter2" class="btn btn-dark">Read More</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card h-100 character-card">
|
||||||
|
<a asp-action="Chapter13">
|
||||||
|
<responsive-image src="pub-from-chapter-13.png" class="fit-image" alt="Pub from Chapter 13" display-width-percentage="50"></responsive-image>
|
||||||
|
</a>
|
||||||
|
<div class="card-body border-top border-3 border-dark">
|
||||||
|
<h3 class="card-title">Chapter 13: A Name She Never Owned — Susie</h3>
|
||||||
|
<p class="card-text">Susie goes out for a drink with a punter. What on earth could go wrong...</p>
|
||||||
|
<div class="text-end"><a asp-action="Chapter13" class="btn btn-dark">Read More</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<!-- Plyr for audio -->
|
||||||
|
<script>
|
||||||
|
const player = new Plyr('audio');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Trailer play/pause via custom button or video click (desktop only) -->
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const v = document.getElementById("trailerLandscape");
|
||||||
|
const playBtn = document.getElementById("trailerPlayBtn");
|
||||||
|
if (!v || !playBtn) return;
|
||||||
|
|
||||||
|
// Hide native controls when JS is active; use custom button instead
|
||||||
|
v.controls = false;
|
||||||
|
|
||||||
|
// Start playback
|
||||||
|
const startPlayback = () => {
|
||||||
|
v.muted = false;
|
||||||
|
v.volume = 1.0;
|
||||||
|
v.play()
|
||||||
|
.then(() => { playBtn.style.display = "none"; })
|
||||||
|
.catch(err => console.warn("Video play failed:", err));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle on video click
|
||||||
|
const togglePlayback = () => {
|
||||||
|
if (v.paused) {
|
||||||
|
startPlayback();
|
||||||
|
} else {
|
||||||
|
v.pause();
|
||||||
|
playBtn.style.display = "block";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Events
|
||||||
|
playBtn.addEventListener("click", (e) => { e.preventDefault(); startPlayback(); });
|
||||||
|
v.addEventListener("click", togglePlayback);
|
||||||
|
|
||||||
|
// Keep button state in sync with native events
|
||||||
|
v.addEventListener("play", () => { playBtn.style.display = "none"; });
|
||||||
|
v.addEventListener("pause", () => { playBtn.style.display = "block"; });
|
||||||
|
v.addEventListener("ended", () => { playBtn.style.display = "block"; });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const el = document.getElementById('fullSynopsis');
|
||||||
|
if (!el || !window.bootstrap?.Collapse) return;
|
||||||
|
|
||||||
|
// Initialise without auto-toggling
|
||||||
|
const c = new bootstrap.Collapse(el, { toggle: false });
|
||||||
|
const mq = window.matchMedia('(min-width: 768px)'); // Bootstrap md
|
||||||
|
|
||||||
|
function setInitial() {
|
||||||
|
if (mq.matches) {
|
||||||
|
// Desktop: keep collapsed
|
||||||
|
c.hide();
|
||||||
|
} else {
|
||||||
|
// Mobile: open by default
|
||||||
|
c.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setInitial();
|
||||||
|
// Optional: if user resizes across the breakpoint, adjust state
|
||||||
|
mq.addEventListener?.('change', setInitial);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@section Meta {
|
||||||
|
<MetaTag meta-title="The Alpha Flame: Discovery by Catherine Lynwood"
|
||||||
|
meta-description="A gritty 1980s Birmingham psycological crime novel about two girls 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-author="Catherine Lynwood"
|
||||||
|
meta-url="https://www.catherinelynwood.com/the-alpha-flame/discovery"
|
||||||
|
meta-image="https://www.catherinelynwood.com/images/webp/the-alpha-flame-discovery-cover-1200.webp"
|
||||||
|
meta-image-png="https://www.catherinelynwood.com/images/the-alpha-flame-discovery-cover.png"
|
||||||
|
meta-image-alt="Maggie from 'The Alpha Flame: Discovery' by Catherine Lynwood"
|
||||||
|
og-site-name="Catherine Lynwood - The Alpha Flame: Discovery"
|
||||||
|
article-published-time="@new DateTime(2024, 11, 20)"
|
||||||
|
article-modified-time="@new DateTime(2025, 09, 10)"
|
||||||
|
twitter-card-type="summary_large_image"
|
||||||
|
twitter-site-handle="@@CathLynwood"
|
||||||
|
twitter-creator-handle="@@CathLynwood" />
|
||||||
|
|
||||||
|
<script type="application/ld+json">
|
||||||
|
@Html.Raw(Model.Reviews.SchemaJsonLd)
|
||||||
|
</script>
|
||||||
|
}
|
||||||
@ -20,29 +20,15 @@
|
|||||||
<!-- HERO: Cover + Trailer + Buy Box -->
|
<!-- HERO: Cover + Trailer + Buy Box -->
|
||||||
<section class="mb-4">
|
<section class="mb-4">
|
||||||
<div class="row g-3 align-items-stretch">
|
<div class="row g-3 align-items-stretch">
|
||||||
<!-- Book Cover -->
|
|
||||||
<div class="col-md-5 d-flex d-none d-md-block">
|
|
||||||
<div class="card character-card h-100 flex-fill" id="cover-card">
|
|
||||||
<responsive-image src="the-alpha-flame-discovery-cover.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">
|
|
||||||
<h3 class="card-title h5 mb-1">The Alpha Flame: <span class="fw-light">Discovery</span></h3>
|
|
||||||
<p class="card-text mb-0">It's 1983 Birmingham. Maggie, my fiery heroine, standing outside the derelict Rubery Hill Hospital. A story of survival, sisters, and fire.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Trailer + Buy Box -->
|
<!-- Trailer + Buy Box -->
|
||||||
<div class="col-md-7 d-flex">
|
<div class="col-12">
|
||||||
<div class="card character-card h-100 flex-fill" id="hero-media-card">
|
<div class="card character-card" id="hero-media-card">
|
||||||
<div class="card-body d-flex flex-column">
|
<div class="card-body">
|
||||||
<!-- Trailer -->
|
<!-- Trailer -->
|
||||||
<div class="trailer-wrapper mb-3">
|
<div class="trailer-wrapper mb-3">
|
||||||
<!-- Mobile / tablet: PORTRAIT -->
|
<!-- Mobile / tablet: PORTRAIT -->
|
||||||
<video id="trailerPortrait"
|
<video id="trailerPortrait"
|
||||||
class="w-100 d-block d-lg-none"
|
class="w-100"
|
||||||
playsinline
|
playsinline
|
||||||
preload="none"
|
preload="none"
|
||||||
poster="/images/webp/the-alpha-flame-discovery-trailer-portrait-400.webp"
|
poster="/images/webp/the-alpha-flame-discovery-trailer-portrait-400.webp"
|
||||||
@ -51,17 +37,6 @@
|
|||||||
Sorry, your browser doesn't support embedded video.
|
Sorry, your browser doesn't support embedded video.
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
<!-- Desktop: LANDSCAPE -->
|
|
||||||
<video id="trailerLandscape"
|
|
||||||
class="w-100 d-none d-lg-block"
|
|
||||||
playsinline
|
|
||||||
preload="none"
|
|
||||||
poster="/images/webp/the-alpha-flame-discovery-trailer-landscape-1400.webp"
|
|
||||||
controls>
|
|
||||||
<source src="/videos/the-alpha-flame-discovery-trailer-landscape.mp4" type="video/mp4">
|
|
||||||
Sorry, your browser doesn't support embedded video.
|
|
||||||
</video>
|
|
||||||
|
|
||||||
<button id="trailerPlayBtn" class="trailer-play-btn">
|
<button id="trailerPlayBtn" class="trailer-play-btn">
|
||||||
<i class="fad fa-play"></i>
|
<i class="fad fa-play"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -188,20 +163,8 @@
|
|||||||
Set in 1983 Birmingham, nearby Redditch, and Barmouth in Wales, The Alpha Flame: Discovery follows the lives of two young women, 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.
|
Set in 1983 Birmingham, nearby Redditch, and Barmouth in Wales, The Alpha Flame: Discovery follows the lives of two young women, 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.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Desktop-only: collapse trigger -->
|
|
||||||
<p class="mb-2 d-none d-md-block">
|
|
||||||
<a class="btn btn-outline-light btn-sm"
|
|
||||||
data-bs-toggle="collapse"
|
|
||||||
href="#fullSynopsis"
|
|
||||||
role="button"
|
|
||||||
aria-expanded="false"
|
|
||||||
aria-controls="fullSynopsis">
|
|
||||||
Read full synopsis
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<!-- One copy of the full synopsis -->
|
<!-- One copy of the full synopsis -->
|
||||||
<div class="collapse" id="fullSynopsis">
|
<div>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<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>
|
||||||
@ -266,82 +229,47 @@
|
|||||||
const player = new Plyr('audio');
|
const player = new Plyr('audio');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Trailer source selection + play button -->
|
<!-- Trailer play/pause via custom button or video click (single video) -->
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
const vPortrait = document.getElementById("trailerPortrait");
|
const v = document.getElementById("trailerPortrait");
|
||||||
const vLandscape = document.getElementById("trailerLandscape");
|
|
||||||
const playBtn = document.getElementById("trailerPlayBtn");
|
const playBtn = document.getElementById("trailerPlayBtn");
|
||||||
if (!playBtn) return;
|
if (!v || !playBtn) return;
|
||||||
|
|
||||||
// Which video is currently visible according to Bootstrap’s lg breakpoint (>=992px)?
|
// Hide native controls when JS is active; use custom button instead
|
||||||
const isDesktop = () => window.matchMedia("(min-width: 992px)").matches;
|
v.controls = false;
|
||||||
const activeVideo = () => isDesktop() ? vLandscape : vPortrait;
|
|
||||||
|
|
||||||
// Hide native controls when JS is active; custom button will start playback
|
// Start playback
|
||||||
[vPortrait, vLandscape].forEach(v => { if (v) v.controls = false; });
|
const startPlayback = () => {
|
||||||
|
|
||||||
// Start playback on whichever video is visible
|
|
||||||
playBtn.addEventListener("click", () => {
|
|
||||||
const v = activeVideo();
|
|
||||||
if (!v) return;
|
|
||||||
v.muted = false;
|
v.muted = false;
|
||||||
v.volume = 1.0;
|
v.volume = 1.0;
|
||||||
v.play().then(() => {
|
v.play()
|
||||||
playBtn.style.display = "none";
|
.then(() => { playBtn.style.display = "none"; })
|
||||||
}).catch(err => console.warn("Video play failed:", err));
|
.catch(err => console.warn("Video play failed:", err));
|
||||||
});
|
};
|
||||||
|
|
||||||
// Toggle pause/play when user clicks on the video itself
|
// Toggle on video click
|
||||||
[vPortrait, vLandscape].forEach(v => {
|
const togglePlayback = () => {
|
||||||
if (!v) return;
|
|
||||||
v.addEventListener("click", () => {
|
|
||||||
if (v.paused) {
|
if (v.paused) {
|
||||||
v.play().then(() => {
|
startPlayback();
|
||||||
playBtn.style.display = "none";
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
v.pause();
|
v.pause();
|
||||||
playBtn.style.display = "block";
|
playBtn.style.display = "block";
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
});
|
|
||||||
|
|
||||||
// If the user starts via OS overlay/natives, also hide the custom button
|
// Events
|
||||||
[vPortrait, vLandscape].forEach(v => {
|
playBtn.addEventListener("click", (e) => { e.preventDefault(); startPlayback(); });
|
||||||
if (!v) return;
|
v.addEventListener("click", togglePlayback);
|
||||||
|
|
||||||
|
// Keep button state in sync with native events
|
||||||
v.addEventListener("play", () => { playBtn.style.display = "none"; });
|
v.addEventListener("play", () => { playBtn.style.display = "none"; });
|
||||||
v.addEventListener("pause", () => { playBtn.style.display = "block"; });
|
v.addEventListener("pause", () => { playBtn.style.display = "block"; });
|
||||||
});
|
v.addEventListener("ended", () => { playBtn.style.display = "block"; });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
|
||||||
const el = document.getElementById('fullSynopsis');
|
|
||||||
if (!el || !window.bootstrap?.Collapse) return;
|
|
||||||
|
|
||||||
// Initialise without auto-toggling
|
|
||||||
const c = new bootstrap.Collapse(el, { toggle: false });
|
|
||||||
const mq = window.matchMedia('(min-width: 768px)'); // Bootstrap md
|
|
||||||
|
|
||||||
function setInitial() {
|
|
||||||
if (mq.matches) {
|
|
||||||
// Desktop: keep collapsed
|
|
||||||
c.hide();
|
|
||||||
} else {
|
|
||||||
// Mobile: open by default
|
|
||||||
c.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setInitial();
|
|
||||||
// Optional: if user resizes across the breakpoint, adjust state
|
|
||||||
mq.addEventListener?.('change', setInitial);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@section Meta {
|
@section Meta {
|
||||||
300
CatherineLynwood/Views/Discovery/IndexMobileB.cshtml
Normal file
300
CatherineLynwood/Views/Discovery/IndexMobileB.cshtml
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
@model CatherineLynwood.Models.DiscoveryPageViewModel
|
||||||
|
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "The Alpha Flame: A Gritty 1980s Birmingham Crime Novel about Twin Sisters";
|
||||||
|
bool showReviews = Model.Reviews.Items.Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
<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">Discovery</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- HERO: Cover + Trailer (no Buy Box here in Version B) -->
|
||||||
|
<section class="mb-4">
|
||||||
|
<div class="row g-3 align-items-stretch">
|
||||||
|
<!-- Trailer only -->
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card character-card" id="hero-media-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<!-- Trailer -->
|
||||||
|
<div class="trailer-wrapper mb-3">
|
||||||
|
<!-- Mobile / tablet: PORTRAIT -->
|
||||||
|
<video id="trailerPortrait"
|
||||||
|
class="w-100"
|
||||||
|
playsinline
|
||||||
|
preload="none"
|
||||||
|
poster="/images/webp/the-alpha-flame-discovery-trailer-portrait-400.webp"
|
||||||
|
controls>
|
||||||
|
<source src="/videos/the-alpha-flame-discovery-trailer-portrait.mp4" type="video/mp4">
|
||||||
|
Sorry, your browser doesn't support embedded video.
|
||||||
|
</video>
|
||||||
|
|
||||||
|
<button id="trailerPlayBtn" class="trailer-play-btn">
|
||||||
|
<i class="fad fa-play"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- If JS is off, hide the custom play button so native controls are used -->
|
||||||
|
<noscript>
|
||||||
|
<style>
|
||||||
|
#trailerPlayBtn {
|
||||||
|
display: none !important
|
||||||
|
}</style>
|
||||||
|
</noscript>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Synopsis first (story-first layout) -->
|
||||||
|
<section id="synopsis" class="mb-4">
|
||||||
|
<div class="card character-card text-white" style="background: url('/images/webp/synopsis-background-960.webp'); background-position: center; background-size: cover;">
|
||||||
|
<div class="card-header">
|
||||||
|
<h2 class="card-title h1 mb-0">The Alpha Flame: <span class="fw-light">Discovery</span></h2>
|
||||||
|
<p class="mb-0">Survival, secrets, and sisters in 1983 Birmingham.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body" id="synopsis-body">
|
||||||
|
<!-- Audio blurb -->
|
||||||
|
<div class="row align-items-center mb-3">
|
||||||
|
<div class="col-2">
|
||||||
|
<responsive-image src="catherine-lynwood-16.png" class="img-fluid rounded-circle border border-2 border-dark" alt="Catherine Lynwood" display-width-percentage="20"></responsive-image>
|
||||||
|
</div>
|
||||||
|
<div class="col-10">
|
||||||
|
<div class="audio-player text-center">
|
||||||
|
<audio id="player">
|
||||||
|
<source src="/audio/the-alpha-flame-discovery-catherine.mp3" type="audio/mpeg">
|
||||||
|
Your browser does not support the audio element.
|
||||||
|
</audio>
|
||||||
|
</div>
|
||||||
|
<p class="text-center text-white small mb-0">Listen to Catherine talking about the book</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Teaser -->
|
||||||
|
<p class="card-text">
|
||||||
|
Set in 1983 Birmingham, nearby Redditch, and Barmouth in Wales, The Alpha Flame: Discovery follows two young women, 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.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- One copy of the full synopsis -->
|
||||||
|
<div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<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">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">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.</p>
|
||||||
|
<p class="card-text">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.</p>
|
||||||
|
<p class="card-text">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.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Social proof near top -->
|
||||||
|
@if (showReviews)
|
||||||
|
{
|
||||||
|
var top = Model.Reviews.Items.Where(x => x.RatingValue == 5).OrderByDescending(y => y.DatePublished).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");
|
||||||
|
|
||||||
|
<section class="mb-4">
|
||||||
|
<div class="card character-card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="h6 text-uppercase text-muted mb-2">★ Reader Praise ★</h3>
|
||||||
|
<blockquote class="blockquote mb-2">
|
||||||
|
<span class="mb-2 text-warning d-inline-block">
|
||||||
|
@for (int i = 0; i < fullStars; i++)
|
||||||
|
{
|
||||||
|
<i class="fad fa-star"></i>
|
||||||
|
}
|
||||||
|
@if (hasHalfStar)
|
||||||
|
{
|
||||||
|
<i class="fad fa-star-half-alt"></i>
|
||||||
|
}
|
||||||
|
@for (int i = 0; i < emptyStars; i++)
|
||||||
|
{
|
||||||
|
<i class="fad fa-star" style="--fa-primary-opacity:0.2;--fa-secondary-opacity:0.2;"></i>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
@Html.Raw(top.ReviewBody)
|
||||||
|
<footer>
|
||||||
|
@top.AuthorName on
|
||||||
|
<cite title="@top.SiteName">
|
||||||
|
@if (string.IsNullOrEmpty(top.URL))
|
||||||
|
{
|
||||||
|
@top.SiteName
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<a href="@top.URL" target="_blank">@top.SiteName</a>
|
||||||
|
}
|
||||||
|
</cite>
|
||||||
|
<span class="text-muted smaller">, @reviewDate</span>
|
||||||
|
</footer>
|
||||||
|
</blockquote>
|
||||||
|
@if (Model.Reviews.Items.Count > 1)
|
||||||
|
{
|
||||||
|
<div class="text-end">
|
||||||
|
<a asp-action="Reviews" class="btn btn-outline-secondary btn-sm">Read more reviews</a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Buy Box now AFTER synopsis/review -->
|
||||||
|
<section class="mb-4" id="buy">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 d-flex">
|
||||||
|
<div class="card character-card h-100 flex-fill" id="buy-card">
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
@* buyBox: server-side slugs + <a ping> tracking *@
|
||||||
|
@* Model: CatherineLynwood.Models.DiscoveryPageViewModel *@
|
||||||
|
@{
|
||||||
|
var L = Model.Buy;
|
||||||
|
string pingBase = "/track/click";
|
||||||
|
string countryIso2 = Model.UserIso2 ?? "GB";
|
||||||
|
string flagPathSvg = $"/images/flags/{countryIso2}.svg";
|
||||||
|
string flagPathPng = $"/images/flags/{countryIso2}.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
<partial name="_BuyBox" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Sticky mobile buy bar (global, still points to #buyBox inside the partial) -->
|
||||||
|
<div id="mobileBuyBar" class="d-md-none fixed-bottom bg-dark text-white py-2 border-top border-3 border-light" style="z-index:1030;">
|
||||||
|
<div class="container d-flex justify-content-between align-items-center">
|
||||||
|
<span class="small">The Alpha Flame: Discovery</span>
|
||||||
|
<a href="#buyBox" class="btn btn-light btn-sm">Buy now</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Chapter Previews -->
|
||||||
|
<section id="chapters" class="mt-4">
|
||||||
|
<h2>Chapter Previews</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card h-100 character-card">
|
||||||
|
<a asp-action="Chapter1">
|
||||||
|
<responsive-image src="beth-stood-in-bathroom.png" class="card-img-top" alt="Beth's Bathroom" 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...</p>
|
||||||
|
<div class="text-end"><a asp-action="Chapter1" class="btn btn-dark">Read More</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card h-100 character-card">
|
||||||
|
<a asp-action="Chapter2">
|
||||||
|
<responsive-image src="maggie-with-her-tr6-2.png" class="fit-image" alt="Maggie With Her TR6" 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 not knowing the story that will pan out before her...</p>
|
||||||
|
<div class="text-end"><a asp-action="Chapter2" class="btn btn-dark">Read More</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 mb-4">
|
||||||
|
<div class="card h-100 character-card">
|
||||||
|
<a asp-action="Chapter13">
|
||||||
|
<responsive-image src="pub-from-chapter-13.png" class="fit-image" alt="Pub from Chapter 13" display-width-percentage="50"></responsive-image>
|
||||||
|
</a>
|
||||||
|
<div class="card-body border-top border-3 border-dark">
|
||||||
|
<h3 class="card-title">Chapter 13, A Name She Never Owned, Susie</h3>
|
||||||
|
<p class="card-text">Susie goes out for a drink with a punter. What on earth could go wrong...</p>
|
||||||
|
<div class="text-end"><a asp-action="Chapter13" class="btn btn-dark">Read More</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<!-- Plyr for audio -->
|
||||||
|
<script>
|
||||||
|
const player = new Plyr('audio');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Trailer play/pause via custom button or video click (single video) -->
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const v = document.getElementById("trailerPortrait");
|
||||||
|
const playBtn = document.getElementById("trailerPlayBtn");
|
||||||
|
if (!v || !playBtn) return;
|
||||||
|
|
||||||
|
// Hide native controls when JS is active; use custom button instead
|
||||||
|
v.controls = false;
|
||||||
|
|
||||||
|
// Start playback
|
||||||
|
const startPlayback = () => {
|
||||||
|
v.muted = false;
|
||||||
|
v.volume = 1.0;
|
||||||
|
v.play()
|
||||||
|
.then(() => { playBtn.style.display = "none"; })
|
||||||
|
.catch(err => console.warn("Video play failed:", err));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toggle on video click
|
||||||
|
const togglePlayback = () => {
|
||||||
|
if (v.paused) {
|
||||||
|
startPlayback();
|
||||||
|
} else {
|
||||||
|
v.pause();
|
||||||
|
playBtn.style.display = "block";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Events
|
||||||
|
playBtn.addEventListener("click", (e) => { e.preventDefault(); startPlayback(); });
|
||||||
|
v.addEventListener("click", togglePlayback);
|
||||||
|
|
||||||
|
// Keep button state in sync with native events
|
||||||
|
v.addEventListener("play", () => { playBtn.style.display = "none"; });
|
||||||
|
v.addEventListener("pause", () => { playBtn.style.display = "block"; });
|
||||||
|
v.addEventListener("ended", () => { playBtn.style.display = "block"; });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@section Meta {
|
||||||
|
<MetaTag meta-title="The Alpha Flame: Discovery by Catherine Lynwood"
|
||||||
|
meta-description="A gritty 1980s Birmingham psycological crime novel about two girls 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-author="Catherine Lynwood"
|
||||||
|
meta-url="https://www.catherinelynwood.com/the-alpha-flame/discovery"
|
||||||
|
meta-image="https://www.catherinelynwood.com/images/webp/the-alpha-flame-discovery-cover-1200.webp"
|
||||||
|
meta-image-png="https://www.catherinelynwood.com/images/the-alpha-flame-discovery-cover.png"
|
||||||
|
meta-image-alt="Maggie from 'The Alpha Flame: Discovery' by Catherine Lynwood"
|
||||||
|
og-site-name="Catherine Lynwood - The Alpha Flame: Discovery"
|
||||||
|
article-published-time="@new DateTime(2024, 11, 20)"
|
||||||
|
article-modified-time="@new DateTime(2025, 09, 10)"
|
||||||
|
twitter-card-type="summary_large_image"
|
||||||
|
twitter-site-handle="@@CathLynwood"
|
||||||
|
twitter-creator-handle="@@CathLynwood" />
|
||||||
|
|
||||||
|
<script type="application/ld+json">
|
||||||
|
@Html.Raw(Model.Reviews.SchemaJsonLd)
|
||||||
|
</script>
|
||||||
|
}
|
||||||
@ -40,10 +40,13 @@
|
|||||||
<div class="col-12 col-sm-6">
|
<div class="col-12 col-sm-6">
|
||||||
<a class="btn btn-dark w-100"
|
<a class="btn btn-dark w-100"
|
||||||
href="@L.IngramHardback.Url"
|
href="@L.IngramHardback.Url"
|
||||||
ping="@($"{pingBase}?slug={L.IngramHardback.Slug}&src=discovery")"
|
ping="@($"/track/click?slug={L.IngramHardback.Slug}&src=discovery")"
|
||||||
rel="nofollow noindex">
|
rel="nofollow noindex">
|
||||||
<i class="fad fa-gem me-1"></i> Hardback, direct
|
<i class="fad fa-gem me-1"></i> Hardback, direct
|
||||||
<span class="price-chip d-none" aria-hidden="true"></span>
|
@if (!string.IsNullOrWhiteSpace(L.IngramHardbackPrice))
|
||||||
|
{
|
||||||
|
<span class="price-chip ms-2">@L.IngramHardbackPrice</span>
|
||||||
|
}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -52,13 +55,17 @@
|
|||||||
<div class="col-12 col-sm-6">
|
<div class="col-12 col-sm-6">
|
||||||
<a class="btn btn-dark w-100"
|
<a class="btn btn-dark w-100"
|
||||||
href="@L.IngramPaperback.Url"
|
href="@L.IngramPaperback.Url"
|
||||||
ping="@($"{pingBase}?slug={L.IngramPaperback.Slug}&src=discovery")"
|
ping="@($"/track/click?slug={L.IngramPaperback.Slug}&src=discovery")"
|
||||||
rel="nofollow noindex">
|
rel="nofollow noindex">
|
||||||
<i class="fad fa-book me-1"></i> Paperback, direct
|
<i class="fad fa-book me-1"></i> Paperback, direct
|
||||||
<span class="price-chip d-none" aria-hidden="true"></span>
|
@if (!string.IsNullOrWhiteSpace(L.IngramPaperbackPrice))
|
||||||
|
{
|
||||||
|
<span class="price-chip ms-2">@L.IngramPaperbackPrice</span>
|
||||||
|
}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,9 @@
|
|||||||
"IndexNow": {
|
"IndexNow": {
|
||||||
"ApiKey": "cc6ff72c3d1a48d0b0b7c2c2b543f15f"
|
"ApiKey": "cc6ff72c3d1a48d0b0b7c2c2b543f15f"
|
||||||
},
|
},
|
||||||
|
"AbTest": {
|
||||||
|
"DiscoveryBPercent": 50
|
||||||
|
},
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user