Moved old project components to this new project
This commit is contained in:
+100
@@ -0,0 +1,100 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build
|
||||
|
||||
steps:
|
||||
- name: dotnet-build
|
||||
image: mcr.microsoft.com/dotnet/sdk:10.0
|
||||
commands:
|
||||
- dotnet restore LiteCharms.slnx
|
||||
- dotnet build LiteCharms.slnx -c Release
|
||||
|
||||
- name: dotnet-test
|
||||
image: mcr.microsoft.com/dotnet/sdk:10.0
|
||||
commands:
|
||||
- dotnet restore LiteCharms.slnx
|
||||
- dotnet test LiteCharms.slnx -c Release --no-restore
|
||||
|
||||
trigger:
|
||||
event: [ pull_request ]
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: package
|
||||
|
||||
steps:
|
||||
- name: docker-build
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: nexus.khongisa.co.za
|
||||
repo: nexus.khongisa.co.za/litecharms-leadgenerator
|
||||
tags: [ latest, "1.${DRONE_BUILD_NUMBER}" ]
|
||||
username: { from_secret: docker_username }
|
||||
password: { from_secret: docker_password }
|
||||
dockerfile: shop/Dockerfile
|
||||
context: .
|
||||
|
||||
- name: gitea-tag
|
||||
image: alpine/git
|
||||
environment:
|
||||
GITEA_USER: { from_secret: git_username }
|
||||
GITEA_PASS: { from_secret: git_password }
|
||||
commands:
|
||||
- git config --global user.email "drone@litecharms.co.za"
|
||||
- git config --global user.name "Drone CI"
|
||||
- git remote set-url origin https://$${GITEA_USER}:$${GITEA_PASS}@gitea.khongisa.co.za/litecharms/leadgenerator.git
|
||||
- git tag 1.${DRONE_BUILD_NUMBER}
|
||||
- git push origin 1.${DRONE_BUILD_NUMBER}
|
||||
|
||||
depends_on:
|
||||
- build
|
||||
|
||||
trigger:
|
||||
event: [ pull_request ]
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: uat
|
||||
|
||||
steps:
|
||||
- name: deploy
|
||||
image: bitnami/kubectl:latest
|
||||
environment:
|
||||
KUBE_CONFIG: { from_secret: kube_config }
|
||||
commands:
|
||||
- mkdir -p $HOME/.kube
|
||||
- echo "$KUBE_CONFIG" > $HOME/.kube/config
|
||||
- kubectl apply -f shop/litecharms-shop-uat.yml
|
||||
- kubectl rollout restart deployment/litecharms-leadgenerator -n litecharms-shop-uat
|
||||
|
||||
depends_on:
|
||||
- package
|
||||
|
||||
trigger:
|
||||
event: [ pull_request ]
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: prod
|
||||
|
||||
steps:
|
||||
- name: deploy
|
||||
image: bitnami/kubectl:latest
|
||||
environment:
|
||||
KUBE_CONFIG: { from_secret: kube_config }
|
||||
commands:
|
||||
- mkdir -p $HOME/.kube
|
||||
- echo "$KUBE_CONFIG" > $HOME/.kube/config
|
||||
- kubectl apply -f litecharms-shop.yml
|
||||
- kubectl rollout restart shop/deployment/litecharms-leadgenerator -n litecharms-shop
|
||||
|
||||
depends_on:
|
||||
- uat
|
||||
|
||||
trigger:
|
||||
event: [ promote ]
|
||||
target: [ production ]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"root": true,
|
||||
"dependentFileProviders": {
|
||||
"add": {
|
||||
"addedExtension": {},
|
||||
"extensionToExtension": {
|
||||
"add": {
|
||||
".css": [ ".razor" ],
|
||||
".cs": [ ".razor" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Stage 1: Build
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /app
|
||||
|
||||
COPY ["../nuget.config", "./"]
|
||||
COPY ["../LeadGenerator/LeadGenerator.csproj", "LeadGenerator/"]
|
||||
RUN dotnet restore "LeadGenerator/LeadGenerator.csproj" --configfile nuget.config
|
||||
COPY . .
|
||||
RUN dotnet publish "../LeadGenerator/LeadGenerator.csproj" -c Release -o /app/publish /p:UseAppHost=false
|
||||
|
||||
# Stage 2: Final Image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
|
||||
WORKDIR /app
|
||||
RUN addgroup --system --gid 1000 appgroup && \
|
||||
adduser --system --uid 1000 --ingroup appgroup appuser
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8081
|
||||
|
||||
COPY --from=build /app/publish .
|
||||
|
||||
RUN chown -R appuser:appgroup /app
|
||||
USER appuser
|
||||
|
||||
ENTRYPOINT ["dotnet", "LeadGenerator.dll"]
|
||||
@@ -0,0 +1,18 @@
|
||||
PROPRIETARY LICENSE
|
||||
|
||||
Copyright (c) 2026 Lite Charms (PTY) Ltd. All rights reserved.
|
||||
|
||||
This software and its associated documentation (the "Software") are the
|
||||
proprietary property of Lite Charms (PTY) Ltd.
|
||||
|
||||
The Software is provided for internal use only. Unauthorized copying,
|
||||
distribution, modification, or use of this file via any medium is
|
||||
strictly prohibited.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
Binary file not shown.
+3
-1
@@ -1 +1,3 @@
|
||||
<Solution />
|
||||
<Solution>
|
||||
<Project Path="Shop/Shop.csproj" Id="1a1797db-15c0-469d-bfcf-c0847dd0b5fc" />
|
||||
</Solution>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<ResourcePreloader />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="@Assets["app.css"]" />
|
||||
<link rel="stylesheet" href="@Assets["Shop.styles.css"]" />
|
||||
<ImportMap />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Routes @rendermode="InteractiveServer" />
|
||||
<ReconnectModal />
|
||||
<script src="@Assets["_framework/blazor.web.js"]"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,147 @@
|
||||
@inherits LayoutComponentBase
|
||||
@inject IJSRuntime JSRuntime
|
||||
@using Blazored.Toast
|
||||
|
||||
<header class="top-bar">
|
||||
<a href="/" class="brand">
|
||||
<svg class="brand-mark" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M70,25 C65,18 58,15 50,15 C30,15 15,30 15,50 C15,70 30,85 50,85 C58,85 65,82 70,75 L62,68 C58,72 54,74 50,74 C37,74 27,63 27,50 C27,37 37,26 50,26 C54,26 58,28 62,32 L70,25 Z" fill="#0096c7" />
|
||||
<circle cx="85" cy="50" r="8" fill="#4dabff" opacity="0.9" />
|
||||
<circle cx="75" cy="80" r="5" fill="#4dabff" opacity="0.6" />
|
||||
<circle cx="75" cy="20" r="5" fill="#4dabff" opacity="0.6" />
|
||||
</svg>
|
||||
|
||||
<div class="text-column">
|
||||
<span class="brand-main">Lite<span class="brand-accent">Charms</span></span>
|
||||
<span class="payoff-line">Affordable Technology Today</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<button class="hamburger" @onclick="ToggleMenu" aria-label="Toggle Menu" aria-expanded="@isMenuOpen.ToString().ToLower()">
|
||||
<div class="bar @(isMenuOpen ? "animate" : "")"></div>
|
||||
</button>
|
||||
|
||||
<nav class="nav-links @(isMenuOpen ? "open" : "")">
|
||||
<a href="services" class="nav-link" @onclick="CloseMenu">Services</a>
|
||||
<a href="shop" class="nav-link nav-shop" @onclick="CloseMenu">Shop</a>
|
||||
<a href="about" class="nav-link" @onclick="CloseMenu">About</a>
|
||||
<a href="contact" class="nav-link" @onclick="CloseMenu">Contact</a>
|
||||
<button class="btn-login mobile-only" @onclick="CloseMenu">Login</button>
|
||||
</nav>
|
||||
|
||||
<div class="header-actions desktop-only">
|
||||
<div class="contact-info">
|
||||
<span class="contact-item">
|
||||
<svg class="contact-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"></path></svg>
|
||||
+27872650198
|
||||
</span>
|
||||
<span class="blue-dot"></span>
|
||||
@* Refactored: Mailto link with subject and body *@
|
||||
<a href="mailto:contact@litecharms.co.za?subject=Enquiry%20from%20Shop&body=Hi%20Lite%20Charms%20Team," class="contact-item">
|
||||
<svg class="contact-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
|
||||
contact@litecharms.co.za
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button class="btn-login">
|
||||
<svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path><polyline points="10 17 15 12 10 7"></polyline><line x1="15" y1="12" x2="3" y2="12"></line></svg>
|
||||
Login
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="page-wrapper">
|
||||
<main>
|
||||
@Body
|
||||
</main>
|
||||
|
||||
@* Cookie Banner Added Here *@
|
||||
@if (showBanner)
|
||||
{
|
||||
<div class="cookie-banner">
|
||||
<div class="cookie-content">
|
||||
<p>We use cookies to enhance your experience. By continuing to visit this site you agree to our use of cookies.</p>
|
||||
<div class="cookie-actions">
|
||||
<button class="btn-cookie accept" @onclick="() => HandleConsent(true)">Accept</button>
|
||||
<button class="btn-cookie decline" @onclick="() => HandleConsent(false)">Decline</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<BlazoredToasts />
|
||||
|
||||
<footer class="bottom-bar">
|
||||
<div class="footer-left">
|
||||
© 2026 Lite Charms (PTY) LTD. All rights reserved.
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<a href="terms" class="footer-link">Terms & Conditions</a>
|
||||
<span class="divider">|</span>
|
||||
<a href="privacy" class="footer-link">Privacy Policy</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private bool isMenuOpen = false;
|
||||
private bool showBanner = false;
|
||||
private bool isInitialized = false;
|
||||
|
||||
private void ToggleMenu()
|
||||
{
|
||||
isMenuOpen = !isMenuOpen;
|
||||
}
|
||||
|
||||
private void CloseMenu()
|
||||
{
|
||||
isMenuOpen = false;
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
// Only run this logic once when the component first renders
|
||||
if (firstRender && !isInitialized)
|
||||
{
|
||||
isInitialized = true;
|
||||
|
||||
try
|
||||
{
|
||||
// Attempt to read from localStorage
|
||||
var consent = await JSRuntime.InvokeAsync<string>("localStorage.getItem", "litecharms_cookie_consent");
|
||||
|
||||
// If no consent is found, show the banner
|
||||
if (string.IsNullOrWhiteSpace(consent))
|
||||
{
|
||||
await Task.Delay(500); // Wait for page to settle
|
||||
showBanner = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If JS Interop fails (e.g., storage blocked), default to showing the banner
|
||||
Console.WriteLine($"Cookie check failed: {ex.Message}");
|
||||
showBanner = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleConsent(bool accepted)
|
||||
{
|
||||
showBanner = false;
|
||||
|
||||
try
|
||||
{
|
||||
// Save preference
|
||||
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "litecharms_cookie_consent", accepted ? "accepted" : "rejected");
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fail silently if storage is unavailable
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/* High-visibility Shop link */
|
||||
.nav-links .nav-link.nav-shop {
|
||||
color: var(--brand-blue, #0096c7);
|
||||
font-weight: 700;
|
||||
border: 1px solid transparent;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-links .nav-link.nav-shop:hover {
|
||||
background-color: rgba(0, 150, 199, 0.1);
|
||||
border-color: var(--brand-blue, #0096c7);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* Mobile adjustments for the shop button */
|
||||
@media (max-width: 768px) {
|
||||
.nav-links .nav-link.nav-shop {
|
||||
border: 1px solid var(--brand-blue, #0096c7);
|
||||
margin: 5px 0;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 150, 199, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Blazored Toast Branded Styling --- */
|
||||
|
||||
/* 1. The Container: Positioned bottom-left */
|
||||
::deep .blazored-toast-container {
|
||||
position: fixed;
|
||||
bottom: 20px; /* Moved from top to bottom */
|
||||
left: 20px; /* Moved from right to left */
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
flex-direction: column-reverse; /* Newest toasts appear on top of old ones */
|
||||
gap: 12px;
|
||||
width: 350px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 2. The Individual Toast */
|
||||
::deep .blazored-toast {
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
background: rgba(11, 17, 20, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(0, 150, 199, 0.3);
|
||||
border-left: 4px solid #0096c7;
|
||||
border-radius: 8px;
|
||||
padding: 16px 40px 16px 16px;
|
||||
color: #fff;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
animation: slideInLeft 0.3s ease-out; /* Updated animation name */
|
||||
}
|
||||
|
||||
/* 3. Heading & Message */
|
||||
::deep .blazored-toast-header {
|
||||
font-weight: 800;
|
||||
font-size: 1rem;
|
||||
color: #0096c7;
|
||||
margin-bottom: 4px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
::deep .blazored-toast-message {
|
||||
font-size: 0.9rem;
|
||||
color: #e0e0e0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* 4. The Close (X) Button */
|
||||
::deep .blazored-toast-close {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #888;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
::deep .blazored-toast-close::before {
|
||||
content: "×";
|
||||
}
|
||||
|
||||
::deep .blazored-toast-close:hover {
|
||||
color: #ff4d4d;
|
||||
}
|
||||
|
||||
/* 5. Progress Bar */
|
||||
::deep .blazored-toast-progressbar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 3px;
|
||||
background: rgba(0, 150, 199, 0.5);
|
||||
}
|
||||
|
||||
/* Entrance Animation: Sliding in from the left */
|
||||
@keyframes slideInLeft {
|
||||
from {
|
||||
transform: translateX(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile Adjustments */
|
||||
@media (max-width: 480px) {
|
||||
::deep .blazored-toast-container {
|
||||
width: calc(100% - 40px);
|
||||
left: 20px;
|
||||
bottom: 20px;
|
||||
top: auto; /* Ensure it stays at the bottom */
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<script type="module" src="@Assets["Components/Layout/ReconnectModal.razor.js"]"></script>
|
||||
|
||||
<dialog id="components-reconnect-modal" data-nosnippet>
|
||||
<div class="components-reconnect-container">
|
||||
<div class="components-rejoining-animation" aria-hidden="true">
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
<p class="components-reconnect-first-attempt-visible">
|
||||
Rejoining the server...
|
||||
</p>
|
||||
<p class="components-reconnect-repeated-attempt-visible">
|
||||
Rejoin failed... trying again in <span id="components-seconds-to-next-attempt"></span> seconds.
|
||||
</p>
|
||||
<p class="components-reconnect-failed-visible">
|
||||
Failed to rejoin.<br />Please retry or reload the page.
|
||||
</p>
|
||||
<button id="components-reconnect-button" class="components-reconnect-failed-visible">
|
||||
Retry
|
||||
</button>
|
||||
<p class="components-pause-visible">
|
||||
The session has been paused by the server.
|
||||
</p>
|
||||
<p class="components-resume-failed-visible">
|
||||
Failed to resume the session.<br />Please retry or reload the page.
|
||||
</p>
|
||||
<button id="components-resume-button" class="components-pause-visible components-resume-failed-visible">
|
||||
Resume
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
@@ -0,0 +1,157 @@
|
||||
.components-reconnect-first-attempt-visible,
|
||||
.components-reconnect-repeated-attempt-visible,
|
||||
.components-reconnect-failed-visible,
|
||||
.components-pause-visible,
|
||||
.components-resume-failed-visible,
|
||||
.components-rejoining-animation {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
|
||||
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
|
||||
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
|
||||
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
|
||||
#components-reconnect-modal.components-reconnect-failed,
|
||||
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
#components-reconnect-modal {
|
||||
background-color: white;
|
||||
width: 20rem;
|
||||
margin: 20vh auto;
|
||||
padding: 2rem;
|
||||
border: 0;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
|
||||
opacity: 0;
|
||||
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
|
||||
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
|
||||
&[open]
|
||||
|
||||
{
|
||||
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#components-reconnect-modal::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.4);
|
||||
animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-slideUp {
|
||||
0% {
|
||||
transform: translateY(30px) scale(0.95);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-fadeInOpacity {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes components-reconnect-modal-fadeOutOpacity {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.components-reconnect-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
#components-reconnect-modal p {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button {
|
||||
border: 0;
|
||||
background-color: #6b9ed2;
|
||||
color: white;
|
||||
padding: 4px 24px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button:hover {
|
||||
background-color: #3b6ea2;
|
||||
}
|
||||
|
||||
#components-reconnect-modal button:active {
|
||||
background-color: #6b9ed2;
|
||||
}
|
||||
|
||||
.components-rejoining-animation {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.components-rejoining-animation div {
|
||||
position: absolute;
|
||||
border: 3px solid #0087ff;
|
||||
opacity: 1;
|
||||
border-radius: 50%;
|
||||
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
|
||||
}
|
||||
|
||||
.components-rejoining-animation div:nth-child(2) {
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
@keyframes components-rejoining-animation {
|
||||
0% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
4.9% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
5% {
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Set up event handlers
|
||||
const reconnectModal = document.getElementById("components-reconnect-modal");
|
||||
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
|
||||
|
||||
const retryButton = document.getElementById("components-reconnect-button");
|
||||
retryButton.addEventListener("click", retry);
|
||||
|
||||
const resumeButton = document.getElementById("components-resume-button");
|
||||
resumeButton.addEventListener("click", resume);
|
||||
|
||||
function handleReconnectStateChanged(event) {
|
||||
if (event.detail.state === "show") {
|
||||
reconnectModal.showModal();
|
||||
} else if (event.detail.state === "hide") {
|
||||
reconnectModal.close();
|
||||
} else if (event.detail.state === "failed") {
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
} else if (event.detail.state === "rejected") {
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
async function retry() {
|
||||
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
|
||||
try {
|
||||
// Reconnect will asynchronously return:
|
||||
// - true to mean success
|
||||
// - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID)
|
||||
// - exception to mean we didn't reach the server (this can be sync or async)
|
||||
const successful = await Blazor.reconnect();
|
||||
if (!successful) {
|
||||
// We have been able to reach the server, but the circuit is no longer available.
|
||||
// We'll reload the page so the user can continue using the app as quickly as possible.
|
||||
const resumeSuccessful = await Blazor.resumeCircuit();
|
||||
if (!resumeSuccessful) {
|
||||
location.reload();
|
||||
} else {
|
||||
reconnectModal.close();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// We got an exception, server is currently unavailable
|
||||
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
|
||||
}
|
||||
}
|
||||
|
||||
async function resume() {
|
||||
try {
|
||||
const successful = await Blazor.resumeCircuit();
|
||||
if (!successful) {
|
||||
location.reload();
|
||||
}
|
||||
} catch {
|
||||
reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed");
|
||||
}
|
||||
}
|
||||
|
||||
async function retryWhenDocumentBecomesVisible() {
|
||||
if (document.visibilityState === "visible") {
|
||||
await retry();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
@page "/about"
|
||||
|
||||
<section class="about-container">
|
||||
<div class="about-hero">
|
||||
<h1>Precision Technology. <span class="highlight">Built for Purpose.</span></h1>
|
||||
<p>At Lite Charms, we don't believe in "budget blackholes." We deliver targeted, cloud-native solutions on budget and on time.</p>
|
||||
</div>
|
||||
|
||||
<div class="mission-grid">
|
||||
<div class="mission-card">
|
||||
<h3>Custom Web & API</h3>
|
||||
<p>From basic templated sites to full enterprise solutions, we develop mobile-friendly, SEO-optimized applications powered by secure, tailored APIs.</p>
|
||||
</div>
|
||||
<div class="mission-card">
|
||||
<h3>Enterprise Ecommerce</h3>
|
||||
<p>Enabling growth through secure financial system integrations—from PayFast to direct payment gateways.</p>
|
||||
</div>
|
||||
<div class="mission-card">
|
||||
<h3>Ad-hoc Engineering</h3>
|
||||
<p>When off-the-shelf products fail to close the gap in your business process, we design and produce the perfect fit for your value proposition.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="delivery-philosophy">
|
||||
<h2>Our Delivery Standards</h2>
|
||||
<ul>
|
||||
<li><strong>Iterative Planning:</strong> We assist in critical product development stages to ensure success and satisfaction.</li>
|
||||
<li><strong>Modern SDLC:</strong> We fully embrace cloud-native app development as well as on-prem solutions.</li>
|
||||
<li><strong>Virtual Collaboration:</strong> Fully virtualized operations enable us to foster teaming and quality production regardless of location.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,69 @@
|
||||
.about-container {
|
||||
padding: 80px 5% 40px;
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.about-hero {
|
||||
text-align: center;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.about-hero h1 {
|
||||
font-size: clamp(2rem, 5vw, 3.5rem);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: var(--brand-blue, #0096c7);
|
||||
}
|
||||
|
||||
.mission-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 30px;
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
|
||||
.mission-card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 30px;
|
||||
border-radius: 12px;
|
||||
border-left: 4px solid var(--brand-blue);
|
||||
}
|
||||
|
||||
.mission-card h3 {
|
||||
margin-bottom: 15px;
|
||||
color: var(--brand-blue);
|
||||
}
|
||||
|
||||
.delivery-philosophy {
|
||||
background: rgba(0, 150, 199, 0.1);
|
||||
padding: 40px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.delivery-philosophy h2 {
|
||||
margin-bottom: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.delivery-philosophy ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.delivery-philosophy li {
|
||||
margin-bottom: 15px;
|
||||
padding-left: 25px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.delivery-philosophy li::before {
|
||||
content: "✓";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--brand-blue);
|
||||
font-weight: bold;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
@page "/contact"
|
||||
|
||||
<section class="contact-view">
|
||||
<div class="contact-header">
|
||||
<h1>Let’s Build <span class="highlight">Something Great.</span></h1>
|
||||
<p>Whether you have a specific project in mind or need strategic technical advice, we're ready to help.</p>
|
||||
</div>
|
||||
|
||||
<div class="contact-grid">
|
||||
<!-- Contact Information -->
|
||||
<div class="contact-info-panel">
|
||||
<div class="info-group">
|
||||
<div class="info-item">
|
||||
<h3>Direct Line</h3>
|
||||
<p>+27 87 265 0198</p>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<h3>Email</h3>
|
||||
<p>contact@litecharms.co.za</p>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<h3>Location</h3>
|
||||
<p>Midrand, Johannesburg</p>
|
||||
<p class="sub-text">High-Tech Hub, Northern Suburbs</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="community-section">
|
||||
<h3>Direct Enquiries</h3>
|
||||
<div class="community-links">
|
||||
<a href="https://discord.com/" target="_blank" class="community-btn discord">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36">
|
||||
<path d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.71,32.65-1.82,56.6.39,80.21a105.73,105.73,0,0,0,32.17,16.15,77.7,77.7,0,0,0,6.89-11.11,68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1,105.25,105.25,0,0,0,32.19-16.14c2.64-27.38-4.52-51.06-19.1-72.13ZM42.45,65.69C36.18,65.69,31,60,31,53s5.07-12.71,11.41-12.71,11.52,5.76,11.41,12.71C53.86,60,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5.07-12.71,11.44-12.71,11.51,5.76,11.44,12.71C96.13,60,91.17,65.69,84.69,65.69Z" />
|
||||
</svg>
|
||||
Discord
|
||||
</a>
|
||||
<a href="https://litecharmsworkspace.slack.com/" target="_blank" class="community-btn slack">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523 2.527 2.527 0 0 1-2.522-2.523 2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.27 0a2.528 2.528 0 0 1 2.521-2.52 2.528 2.528 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.833 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.833 5.042a2.528 2.528 0 0 1-2.521-2.52A2.527 2.527 0 0 1 8.833 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.833zm0 1.27a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.833a2.528 2.528 0 0 1 2.522-2.521h6.311zm10.125 3.815a2.528 2.528 0 0 1 2.52 2.52 2.528 2.528 0 0 1-2.52 2.523h-2.522v-2.523a2.528 2.528 0 0 1 2.522-2.52zm-1.27 0a2.528 2.528 0 0 1-2.521 2.52 2.528 2.528 0 0 1-2.521-2.52V3.833A2.528 2.528 0 0 1 15.167 1.31a2.528 2.528 0 0 1 2.521 2.522v6.313zM15.167 18.958a2.528 2.528 0 0 1 2.521 2.522A2.527 2.527 0 0 1 15.167 24a2.528 2.528 0 0 1-2.522-2.52v-2.522h2.522zm0-1.27a2.528 2.528 0 0 1-2.522-2.521 2.528 2.528 0 0 1 2.522-2.521h6.311a2.528 2.528 0 0 1 2.522 2.521 2.528 2.528 0 0 1-2.522 2.521h-6.311z" />
|
||||
</svg>
|
||||
Slack
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="availability-badge">
|
||||
<span class="pulse-dot"></span>
|
||||
Currently accepting new projects for 2026
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Form Container -->
|
||||
<div class="form-container">
|
||||
<EditForm Model="Input" OnValidSubmit="SendEmailAsync" class="contact-form">
|
||||
<DataAnnotationsValidator />
|
||||
|
||||
<div class="form-group">
|
||||
<label>Full Name</label>
|
||||
<InputText @bind-Value="Input.FullName" placeholder="e.g. Sbanibani Mabaso" />
|
||||
<ValidationMessage For="@(() => Input.FullName)" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Work Email</label>
|
||||
<InputText @bind-Value="Input.EmailAddress" placeholder="name@company.co.za" />
|
||||
<ValidationMessage For="@(() => Input.EmailAddress)" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Interest</label>
|
||||
<InputSelect @bind-Value="Input.EmailSubject">
|
||||
<option value="" disabled selected>Select an option...</option>
|
||||
<option>Software Development</option>
|
||||
<option>Cloud/Kubernetes Infrastructure</option>
|
||||
<option>Ecommerce Solutions</option>
|
||||
<option>Strategic Consulting</option>
|
||||
<option>Other</option>
|
||||
</InputSelect>
|
||||
<ValidationMessage For="@(() => Input.EmailSubject)" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Message</label>
|
||||
<InputTextArea @bind-Value="Input.Message" placeholder="Tell us about your project..." rows="4" />
|
||||
<ValidationMessage For="@(() => Input.Message)" />
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-submit">Send Message</button>
|
||||
</EditForm>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,45 @@
|
||||
using LiteCharms.Features.Utilities.Commands;
|
||||
using LiteCharms.Models;
|
||||
|
||||
namespace Shop.Components.Pages;
|
||||
|
||||
public partial class Contact(ISender mediator, IToastService toastService) : IDisposable
|
||||
{
|
||||
public CancellationTokenSource TokenSource { get; set; } = new();
|
||||
|
||||
public EmailEnquiry Input { get; set; } = new();
|
||||
|
||||
public async Task SendEmailAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = SendEmailCommand.Create(Input.EmailAddress!, Input.FullName!, "shop@litecharms.co.za",
|
||||
"Khongisa Shop", Input.EmailSubject!, Input.Message!);
|
||||
|
||||
var result = await mediator.Send(request, TokenSource.Token);
|
||||
|
||||
if (result.IsFailed)
|
||||
{
|
||||
toastService.ShowSuccess("Failed to send email to the team, please try again, alternatively use the Discord / Slack button to contact us", "Lite Charms Team");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
toastService.ShowSuccess("Thank you, " + Input.FullName + ". We will get back to you shortly.", "Lite Charms Team");
|
||||
|
||||
Input = new EmailEnquiry();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
toastService.ShowSuccess(ex.Message, "Lite Charms Team");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
TokenSource.Cancel();
|
||||
TokenSource.Dispose();
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/* --- 1. Layout & Header Fixes --- */
|
||||
.contact-view {
|
||||
padding: 15px 5% 40px;
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.contact-header {
|
||||
text-align: center;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.contact-header h1 {
|
||||
font-size: clamp(2rem, 5vw, 3rem) !important;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
color: #0096c7;
|
||||
}
|
||||
|
||||
.contact-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
gap: 60px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* --- 2. Info Panel & Community Icons --- */
|
||||
.contact-info-panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.info-item h3 {
|
||||
color: #0096c7;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.info-item p {
|
||||
font-size: 1.2rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sub-text {
|
||||
font-size: 0.85rem !important;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.community-section h3 {
|
||||
color: #0096c7;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
margin-bottom: 15px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.community-links {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.community-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 18px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.community-btn svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
fill: #fff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.community-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
transform: translateY(-2px);
|
||||
border-color: #0096c7;
|
||||
}
|
||||
|
||||
.community-btn.discord:hover {
|
||||
color: #5865F2;
|
||||
border-color: #5865F2;
|
||||
background: rgba(88, 101, 242, 0.1);
|
||||
box-shadow: 0 0 15px rgba(88, 101, 242, 0.4);
|
||||
}
|
||||
|
||||
.community-btn.discord:hover svg {
|
||||
fill: #5865F2;
|
||||
}
|
||||
|
||||
.community-btn.slack:hover {
|
||||
color: #ECB22E;
|
||||
border-color: #ECB22E;
|
||||
background: rgba(236, 178, 46, 0.1);
|
||||
box-shadow: 0 0 15px rgba(236, 178, 46, 0.4);
|
||||
}
|
||||
|
||||
.community-btn.slack:hover svg {
|
||||
fill: #ECB22E;
|
||||
}
|
||||
|
||||
/* --- 3. Form Styling & Zero-Height Validation --- */
|
||||
.form-container {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
padding: 40px;
|
||||
border-radius: 15px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 28px;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.9rem;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
::deep .validation-message {
|
||||
position: absolute;
|
||||
bottom: -18px;
|
||||
left: 0;
|
||||
font-size: 0.75rem;
|
||||
color: #ff4d4d;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
::deep input, ::deep select, ::deep textarea {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
background: rgba(0, 0, 0, 0.4) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
||||
border-radius: 6px;
|
||||
color: #fff !important;
|
||||
font-family: inherit;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
::deep input:focus, ::deep select:focus, ::deep textarea:focus {
|
||||
border-color: #0096c7 !important;
|
||||
background: rgba(0, 0, 0, 0.6) !important;
|
||||
}
|
||||
|
||||
/* --- 4. Custom Dropdown & Submit --- */
|
||||
::deep select {
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%230096c7' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E") !important;
|
||||
background-repeat: no-repeat !important;
|
||||
background-position: calc(100% - 15px) center !important;
|
||||
}
|
||||
|
||||
::deep select option {
|
||||
background-color: #0b1114;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
background: #0096c7;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.btn-submit:hover {
|
||||
background: #0077a3;
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
/* Availability Badge */
|
||||
.availability-badge {
|
||||
align-self: flex-start;
|
||||
margin-top: 10px;
|
||||
padding: 12px;
|
||||
background: rgba(0, 255, 150, 0.05);
|
||||
border: 1px solid rgba(0, 255, 150, 0.2);
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.pulse-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #00ff96;
|
||||
border-radius: 50%;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(0, 255, 150, 0.7);
|
||||
}
|
||||
|
||||
70% {
|
||||
transform: scale(1);
|
||||
box-shadow: 0 0 0 10px rgba(0, 255, 150, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(0.95);
|
||||
box-shadow: 0 0 0 0 rgba(0, 255, 150, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.contact-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.contact-view {
|
||||
padding: 30px 5% 40px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
@page "/Error"
|
||||
@using System.Diagnostics
|
||||
|
||||
<PageTitle>Error</PageTitle>
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
|
||||
@code{
|
||||
[CascadingParameter]
|
||||
private HttpContext? HttpContext { get; set; }
|
||||
|
||||
private string? RequestId { get; set; }
|
||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
protected override void OnInitialized() =>
|
||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
@page "/"
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<section class="hero-section" style="text-align: center; display: flex; flex-direction: column; align-items: center; padding-bottom: 80px;">
|
||||
<div class="hero-content">
|
||||
<h1 style="font-size: 4rem; font-weight: 800; margin-bottom: 10px;">
|
||||
Fast, <span style="color: var(--brand-blue);">Reliable</span> Technology Services
|
||||
</h1>
|
||||
<p style="color: var(--payoff-color); font-size: 1.25rem; margin-bottom: 50px;">
|
||||
Software Builds, Server (VPS) Configuration, Secure Tunnels, Private VPN, Private Cloud Builds
|
||||
</p>
|
||||
|
||||
<div class="cta-container">
|
||||
<button class="btn-calltoaction-blue" @onclick="NavigateToShop" style="margin-right: 5;">Go to Shop</button>
|
||||
<button class="btn-calltoaction-white" @onclick="NavigateToServices">View Services</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Highlights Section (The Red Rectangle Area) -->
|
||||
<div class="highlights-container">
|
||||
<!-- Item 1: Fast Turnaround -->
|
||||
<div class="highlight-item">
|
||||
<div class="highlight-icon icon-blue">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><polyline points="12 6 12 12 16 14"></polyline></svg>
|
||||
</div>
|
||||
<div class="highlight-title">Fast Turnaround</div>
|
||||
<div class="highlight-desc">Most builds and configurations deployed same day</div>
|
||||
</div>
|
||||
|
||||
<!-- Item 2: Quality Guaranteed -->
|
||||
<div class="highlight-item">
|
||||
<div class="highlight-icon icon-green">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>
|
||||
</div>
|
||||
<div class="highlight-title">Quality Guaranteed</div>
|
||||
<div class="highlight-desc">Corporate-grade standards applied to every project</div>
|
||||
</div>
|
||||
|
||||
<!-- Item 3: Expert Strategy -->
|
||||
<div class="highlight-item">
|
||||
<div class="highlight-icon icon-gold">
|
||||
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"></path></svg>
|
||||
</div>
|
||||
<div class="highlight-title">Technical Strategy</div>
|
||||
<div class="highlight-desc">Deep expertise in .NET and Kubernetes ecosystems</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@code {
|
||||
private void NavigateToServices()
|
||||
{
|
||||
// Programmatic navigation to the specified route
|
||||
NavigationManager.NavigateTo("/services");
|
||||
}
|
||||
|
||||
private void NavigateToShop()
|
||||
{
|
||||
// Programmatic navigation to the specified route
|
||||
NavigationManager.NavigateTo("/shop");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
/* The main container for your Landing/Hero section */
|
||||
.hero-section {
|
||||
width: 100%;
|
||||
/* FIX: Force the hero to take up exactly the remaining vertical space */
|
||||
height: calc(100vh - 142px); /* Header(92) + Footer(50) = 142 */
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
/* MODIFIED: Changed from 'center' to 'flex-start' so we can manually space it */
|
||||
justify-content: flex-start;
|
||||
/* Aggressive overflow control to stop the "1px leak" */
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
background-color: #001219;
|
||||
background-image: radial-gradient(at 0% 0%, rgba(0, 95, 115, 0.15) 0px, transparent 50%), radial-gradient(at 100% 100%, rgba(10, 147, 150, 0.12) 0px, transparent 50%), radial-gradient(at 80% 20%, rgba(0, 180, 216, 0.08) 0px, transparent 40%);
|
||||
}
|
||||
|
||||
/* Invisible spacer before the content to "push" it down visually */
|
||||
.hero-section::before {
|
||||
content: "";
|
||||
/* This pushes the content block down by a fluid amount of available space */
|
||||
flex-grow: 1;
|
||||
max-height: 20vh; /* Limits how far it goes on huge screens */
|
||||
min-height: 5vh; /* Minimum safe distance from header on laptops */
|
||||
}
|
||||
|
||||
/* Ensure other overlays remain z-indexed correctly */
|
||||
.hero-section::after {
|
||||
content: "";
|
||||
/* (keep your subtle grid overlays here) */
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
position: relative;
|
||||
z-index: 10; /* Keep content forward */
|
||||
text-align: center;
|
||||
padding: 0 2rem;
|
||||
/* Standard constraints */
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
/* FIX: Make the content itself a flexible stack so it can shrink */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
/* Proportional spacing between ALL core elements (h1 -> p -> buttons -> icons) */
|
||||
gap: clamp(1vh, 3vh, 30px);
|
||||
}
|
||||
|
||||
/* Typography Spacing Fixes */
|
||||
.hero-content h1 {
|
||||
/* Responsive text that shrinks on smaller laptop viewports */
|
||||
font-size: clamp(2rem, 5.5vh, 3.5rem);
|
||||
line-height: 1.1;
|
||||
color: #fff;
|
||||
margin: 0; /* Reset margins in favor of gap */
|
||||
}
|
||||
|
||||
.hero-content p {
|
||||
font-size: clamp(1rem, 2vh, 1.25rem);
|
||||
color: var(--payoff-color);
|
||||
margin: 0; /* Reset margins in favor of gap */
|
||||
}
|
||||
|
||||
/* FIX: Refactored Call to Action Container */
|
||||
.cta-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
/* Removed margin-bottom in favor of hero-content gap */
|
||||
/* Keep a minimum height for the buttons themselves, but no max-height */
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
/* Ensure the button borders/shadows don't cause the 1px leak */
|
||||
.btn-calltoaction-blue, .btn-calltoaction-white {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Features Grid Spacing */
|
||||
.features-grid {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: 20px;
|
||||
/* Optional: Ensure this section isn't stretching */
|
||||
align-items: flex-start;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
@page "/not-found"
|
||||
@layout Layout.MainLayout
|
||||
|
||||
<h3>Not Found</h3>
|
||||
<p>Sorry, the content you are looking for does not exist.</p>
|
||||
@@ -0,0 +1,40 @@
|
||||
@page "/privacy"
|
||||
|
||||
<div class="privacy-page">
|
||||
<div class="privacy-container">
|
||||
<h1>Privacy Policy</h1>
|
||||
<p class="last-updated">Last Updated: May 2026</p>
|
||||
|
||||
<p>At Lite Charms (PTY) LTD, we value your privacy. This policy explains how we collect, use, and protect your personal information in accordance with South African and global data protection standards.</p>
|
||||
|
||||
<h3>1. Legal Framework (POPI Act)</h3>
|
||||
<p>In accordance with the <strong>Protection of Personal Information Act (POPIA)</strong> of South Africa, we are committed to protecting the privacy of our clients. We act as the "Responsible Party" for any data processed through our services.</p>
|
||||
|
||||
<h3>2. Information We Collect</h3>
|
||||
<p>We only collect information necessary to provide our software development and infrastructure services, including:</p>
|
||||
<ul>
|
||||
<li><strong>Contact Details:</strong> Name, email address, and phone number provided via our contact forms.</li>
|
||||
<li><strong>Technical Data:</strong> IP addresses and browser types for security and site optimization.</li>
|
||||
<li><strong>Transaction Data:</strong> Details required to process electronic payments and issue quotes.</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. How We Use Your Data</h3>
|
||||
<p>Your data is used strictly for professional purposes:</p>
|
||||
<ul>
|
||||
<li>To provide "best effort" quotes and fulfill service agreements.</li>
|
||||
<li>To manage the free 3-month support period and 73-hour SLAs.</li>
|
||||
<li>To communicate regarding 50% upfront fees or project updates.</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Data Security and Storage</h3>
|
||||
<p>We implement industry-standard security measures to prevent unauthorized access. Given our expertise in corporate-grade Kubernetes and automation, we apply the same high-security principles to our internal data management. We do not sell or lease your personal information to third parties.</p>
|
||||
|
||||
<h3>5. Your Rights</h3>
|
||||
<p>Under <strong>POPIA</strong> and global standards, you have the right to:</p>
|
||||
<ul>
|
||||
<li>Request access to the personal information we hold about you.</li>
|
||||
<li>Request the correction or deletion of your data.</li>
|
||||
<li>Object to the processing of your data for marketing purposes.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,58 @@
|
||||
/* --- Privacy Policy Styling (Adapted from Terms) --- */
|
||||
.privacy-page {
|
||||
width: 100%;
|
||||
background-color: var(--bar-bg);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.privacy-container {
|
||||
max-width: 900px;
|
||||
width: 90%;
|
||||
padding: 60px 0;
|
||||
line-height: 1.8;
|
||||
color: var(--text-white);
|
||||
}
|
||||
|
||||
.privacy-container h1 {
|
||||
font-size: 2.5rem;
|
||||
color: var(--brand-blue);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.privacy-container .last-updated {
|
||||
color: var(--payoff-color);
|
||||
font-style: italic;
|
||||
margin-bottom: 40px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.privacy-container h3 {
|
||||
color: var(--brand-blue);
|
||||
font-size: 1.4rem;
|
||||
margin-top: 35px;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 1px solid rgba(0, 150, 199, 0.2);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.privacy-container p {
|
||||
margin-bottom: 20px;
|
||||
text-align: justify;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.privacy-container ul {
|
||||
margin-bottom: 25px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.privacy-container li {
|
||||
margin-bottom: 12px;
|
||||
color: var(--text-white);
|
||||
}
|
||||
|
||||
.privacy-container strong {
|
||||
color: var(--payoff-color);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
@page "/services"
|
||||
|
||||
<div class="services-page">
|
||||
<div class="services-header">
|
||||
<h1>Our Services</h1>
|
||||
<p class="payoff-text">Bespoke technical solutions from infrastructure to the handheld.</p>
|
||||
</div>
|
||||
|
||||
<div class="category-nav">
|
||||
<button class="nav-btn @(activeTab == "dev" ? "active" : "")" @onclick='() => activeTab = "dev"'>Software Development</button>
|
||||
<button class="nav-btn @(activeTab == "infra" ? "active" : "")" @onclick='() => activeTab = "infra"'>Infrastructure & DevOps</button>
|
||||
<button class="nav-btn @(activeTab == "expert" ? "active" : "")" @onclick='() => activeTab = "expert"'>Specialized & Advisory</button>
|
||||
</div>
|
||||
|
||||
<div class="services-grid">
|
||||
@if (activeTab == "dev")
|
||||
{
|
||||
<div class="service-card border-dev">
|
||||
<h3>
|
||||
<svg class="service-icon icon-dev" viewBox="0 0 24 24"><path d="M20,18H4V6H20M20,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V6C22,4.89 21.1,4 20,4M12,17L10,15L12,13L14,15L12,17M18,17L16,15L18,13L20,15L18,17M6,17L4,15L6,13L8,15L6,17Z" /></svg>
|
||||
Desktop Applications
|
||||
</h3>
|
||||
<p>We specialize in engineering high-performance, native desktop software tailored for Windows, macOS, and Linux environments. Our development process prioritizes resource efficiency and hardware integration.</p>
|
||||
<ul class="service-benefits">
|
||||
<li>Native performance for Windows, Mac, and Linux.</li>
|
||||
<li>Industrial system automation integration.</li>
|
||||
<li>Zero-bloat architecture for mission-critical tasks.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="service-card border-dev">
|
||||
<h3>
|
||||
<svg class="service-icon icon-dev" viewBox="0 0 24 24"><path d="M17,19H7V5H17M17,1H7C5.89,1 5,1.89 5,3V21A2,2 0 0,0 7,23H17A2,2 0 0,0 19,21V3C19,1.89 18.1,1 17,1M12,20A1,1 0 0,1 11,19A1,1 0 0,1 12,18A1,1 0 0,1 13,19A1,1 0 0,1 12,20Z" /></svg>
|
||||
Smartphone Apps
|
||||
</h3>
|
||||
<p>Modernize your commercial workflows with robust, cross-platform Android and iOS applications. We focus on secure, scalable mobile solutions that empower your workforce with real-time data.</p>
|
||||
<ul class="service-benefits">
|
||||
<li>Cross-platform builds for Android and iPhone.</li>
|
||||
<li>Commercial utility focus over consumer gaming.</li>
|
||||
<li>Seamless backend field data synchronization.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="service-card border-dev">
|
||||
<h3>
|
||||
<svg class="service-icon icon-dev" viewBox="0 0 24 24"><path d="M10.59,13.41C11,13.8 11,14.44 10.59,14.83C10.2,15.22 9.56,15.22 9.17,14.83C7.22,12.88 7.22,9.71 9.17,7.76L12.71,4.22C14.66,2.27 17.83,2.27 19.78,4.22C21.73,6.17 21.73,9.34 19.78,11.29L18.29,12.78C18.3,12.28 18.22,11.78 18.06,11.3L19.07,10.29C20.24,9.12 20.24,7.22 19.07,6.05C17.9,4.88 16,4.88 14.83,6.05L11.29,9.59C10.12,10.76 10.12,12.66 11.29,13.83L10.59,13.41M5.71,19.78C3.76,17.83 3.76,14.66 5.71,12.71L7.22,11.22C7.21,11.72 7.3,12.22 7.46,12.7L6.44,13.71C5.27,14.88 5.27,16.78 6.44,17.95C7.61,19.12 9.5,19.12 10.67,17.95L14.21,14.41C15.38,13.24 15.38,11.34 14.21,10.17L14.92,10.59C14.5,10.2 14.5,9.56 14.92,9.17C15.31,8.78 15.95,8.78 16.34,9.17C18.29,11.12 18.29,14.29 16.34,16.24L12.78,19.78C10.83,21.73 7.66,21.73 5.71,19.78Z" /></svg>
|
||||
Web API & Apps
|
||||
</h3>
|
||||
<p>We architect scalable digital ecosystems using industry-standard protocols. Our builds feature OIDC authentication and fully interactive documentation for rapid developer integration.</p>
|
||||
<ul class="service-benefits">
|
||||
<li>Secure OIDC-backed authentication.</li>
|
||||
<li>High-availability ingestion portals.</li>
|
||||
<li>Architecture designed for horizontal growth.</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
else if (activeTab == "infra")
|
||||
{
|
||||
<div class="service-card border-infra">
|
||||
<h3>
|
||||
<svg class="service-icon icon-infra" viewBox="0 0 24 24"><path d="M4,17H20V15H4V17M4,13H20V11H4V13M4,9H20V7H4V9M2,3H22V5H2V3M2,19H22V21H2V19Z" /></svg>
|
||||
Server Configuration
|
||||
</h3>
|
||||
<p>Ensure your virtual infrastructure is hardened and production-ready. We focus on securing client-owned machines through rigorous security audits and proactive hardening.</p>
|
||||
<ul class="service-benefits">
|
||||
<li>Deep security auditing and hardening.</li>
|
||||
<li>Comprehensive efficiency sanity checks.</li>
|
||||
<li>Bank-grade self-hosting standards.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="service-card border-infra">
|
||||
<h3>
|
||||
<svg class="service-icon icon-infra" viewBox="0 0 24 24"><path d="M3,15V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V15H19V19H5V15H3M12,16L17,11H14V3H10V11H7L12,16Z" /></svg>
|
||||
Docker App Server
|
||||
</h3>
|
||||
<p>Deploy with confidence using containerized environments. We build robust Docker server instances utilizing Dockhand and Howzer for automated lifecycle management.</p>
|
||||
<ul class="service-benefits">
|
||||
<li>Private communication via Pangolin Tunneling.</li>
|
||||
<li>Automated tool-based lifecycle management.</li>
|
||||
<li>Shielded infrastructure architecture.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="service-card border-infra">
|
||||
<h3>
|
||||
<svg class="service-icon icon-infra" viewBox="0 0 24 24"><path d="M12,2L4.5,20.29L5.21,21L12,18L18.79,21L19.5,20.29L12,2Z" /></svg>
|
||||
K3s Cluster
|
||||
</h3>
|
||||
<p>We implement HA K3s orchestrations designed to rival bank infrastructure. Our builds leverage external databases to ensure uptime even during hardware failures.</p>
|
||||
<ul class="service-benefits">
|
||||
<li>High Availability (HA) cluster configurations.</li>
|
||||
<li>Open-source visibility through Headlamp.</li>
|
||||
<li>Resilient startup logic and database backends.</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
else if (activeTab == "expert")
|
||||
{
|
||||
<div class="service-card border-expert">
|
||||
<h3>
|
||||
<svg class="service-icon icon-expert" viewBox="0 0 24 24"><path d="M12,1L3,5V11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11V5L12,1M12,7C13.1,7 14,7.9 14,9C14,10.1 13.1,11 12,11C10.9,11 10,10.1 10,9C10,7.9 10.9,7 12,7M12,13C13.33,13 16,13.67 16,15V16H8V15C8,13.67 10.67,13 12,13Z" /></svg>
|
||||
Identity Services
|
||||
</h3>
|
||||
<p>Take control of your organization’s security with dedicated identity management. We specialize in Authentik and Keycloak for a centralized "Zero-Trust" gateway.</p>
|
||||
<ul class="service-benefits">
|
||||
<li>Private Keycloak and Authentik deployment.</li>
|
||||
<li>Sovereign data management via dedicated backends.</li>
|
||||
<li>Custom corporate branding options.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="service-card border-expert">
|
||||
<h3>
|
||||
<svg class="service-icon icon-expert" viewBox="0 0 24 24"><path d="M12,3L1,9L12,15L21,10.09V17H23V9M5,13.18V17.18L12,21.18L19,17.18V13.18L12,17.18L5,13.18Z" /></svg>
|
||||
Consultation
|
||||
</h3>
|
||||
<p>Leverage expert advisory for AI integration and cybersecurity. We provide deep-dive code audits and tutorage in Python and C# for academic and professional excellence.</p>
|
||||
<ul class="service-benefits">
|
||||
<li>Strategic advisory on AI and Security.</li>
|
||||
<li>Specialized Tutorage for university researchers.</li>
|
||||
<li>Professional code reviews on bank standards.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="service-card spotlight">
|
||||
<h3>
|
||||
<svg class="service-icon icon-gold" viewBox="0 0 24 24"><path d="M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L5,8.09V15.91L12,19.85L19,15.91V8.09L12,4.15Z" /></svg>
|
||||
Enterprise Turn-Key
|
||||
</h3>
|
||||
<p>Our ultimate service provides a complete digital transformation. We build a unified, high-availability ecosystem from infrastructure to custom applications ready for production.</p>
|
||||
<ul class="service-benefits">
|
||||
<li>Full-stack builds from metal to handheld.</li>
|
||||
<li>Integrated cloud clusters and secure tunneling.</li>
|
||||
<li>Single-vendor accountability for the whole stack.</li>
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string activeTab = "dev";
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
/* --- Page Layout & Height Logic --- */
|
||||
.services-page {
|
||||
padding: 40px 4rem;
|
||||
background-color: var(--bar-bg);
|
||||
/* CALC: Viewport height minus header (92px) and footer (50px) */
|
||||
min-height: calc(100vh - 142px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.services-page {
|
||||
padding: 40px 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Header Section --- */
|
||||
.services-header {
|
||||
text-align: center;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.services-header h1 {
|
||||
font-size: 3rem;
|
||||
color: var(--brand-blue);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.payoff-text {
|
||||
color: var(--payoff-color);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
/* --- Category Navigation --- */
|
||||
.category-nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 50px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.nav-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-btn:hover {
|
||||
color: var(--payoff-color);
|
||||
}
|
||||
|
||||
.nav-btn.active {
|
||||
color: var(--brand-blue);
|
||||
text-shadow: 0 0 15px rgba(0, 150, 199, 0.5);
|
||||
}
|
||||
|
||||
/* The animated underline */
|
||||
.nav-btn.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -11px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: var(--brand-blue);
|
||||
box-shadow: 0 0 10px var(--brand-blue);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* --- Services Grid Logic --- */
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||||
gap: 30px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
align-items: start; /* Precents cards from stretching vertically */
|
||||
animation: fadeIn 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Service Card (Core Styling) --- */
|
||||
.service-card {
|
||||
background: rgba(10, 25, 41, 0.7);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
padding: 30px; /* Text doesn't touch borders */
|
||||
border-radius: 12px;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* The standard hover "lift" effect */
|
||||
.service-card:not(.spotlight):hover {
|
||||
transform: translateY(-10px);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Typography Spacing */
|
||||
.service-card h3 {
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
color: var(--brand-blue);
|
||||
}
|
||||
|
||||
.service-card p {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
margin-bottom: 25px; /* Space between description and benefits */
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* The inline SVG icons */
|
||||
.service-icon {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex-shrink: 0; /* Prevents squishing */
|
||||
}
|
||||
|
||||
/* --- Color Variances by Category --- */
|
||||
|
||||
/* 1. Software Development (Default palette) */
|
||||
.icon-dev {
|
||||
fill: #48cae4;
|
||||
filter: drop-shadow(0 0 5px #48cae4);
|
||||
}
|
||||
|
||||
.border-dev:hover {
|
||||
border-color: #0096c7;
|
||||
box-shadow: 0 10px 30px rgba(0, 150, 199, 0.15);
|
||||
}
|
||||
|
||||
.icon-dev + .benefit-arrow {
|
||||
color: #0096c7;
|
||||
text-shadow: 0 0 5px #0096c7;
|
||||
}
|
||||
|
||||
/* 2. Infrastructure (Green palette) */
|
||||
.icon-infra {
|
||||
fill: #06d6a0;
|
||||
filter: drop-shadow(0 0 5px #06d6a0);
|
||||
}
|
||||
|
||||
.border-infra:hover {
|
||||
border-color: #06d6a0;
|
||||
box-shadow: 0 10px 30px rgba(6, 214, 160, 0.15);
|
||||
}
|
||||
|
||||
/* 3. Expert (Purple palette) */
|
||||
.icon-expert {
|
||||
fill: #f72585;
|
||||
filter: drop-shadow(0 0 5px #f72585);
|
||||
}
|
||||
|
||||
.border-expert:hover {
|
||||
border-color: #b5179e;
|
||||
box-shadow: 0 10px 30px rgba(181, 23, 158, 0.15);
|
||||
}
|
||||
|
||||
/* --- Spotlight Card (Enterprise Turn-Key) --- */
|
||||
.spotlight {
|
||||
border: 1px solid #ffd700;
|
||||
background: linear-gradient(145deg, rgba(255, 215, 0, 0.05), rgba(0, 18, 25, 1));
|
||||
}
|
||||
|
||||
.spotlight:hover {
|
||||
box-shadow: 0 15px 35px rgba(255, 215, 0, 0.15);
|
||||
}
|
||||
|
||||
.spotlight h3 {
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.icon-gold {
|
||||
fill: #ffd700;
|
||||
filter: drop-shadow(0 0 8px #ffd700);
|
||||
}
|
||||
|
||||
/* --- Refactored Bullet Point Logic --- */
|
||||
.service-benefits {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding-top: 20px;
|
||||
margin-top: auto; /* Pushes list to the bottom */
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.service-benefits li {
|
||||
font-size: 0.9rem;
|
||||
color: var(--payoff-color);
|
||||
margin-bottom: 12px;
|
||||
padding-left: 25px; /* Creates gutter for the absolute bullet */
|
||||
/* FIX: Set positioning context to ensure ::before stays inside the li */
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Custom glowing bullet point */
|
||||
.service-benefits li::before {
|
||||
content: '→';
|
||||
position: absolute;
|
||||
left: 0; /* Aligns within the li gutter */
|
||||
top: 0; /* Vertically aligns with text start */
|
||||
/* These specific colors should match the card category (set via JS if needed) */
|
||||
color: var(--brand-blue);
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 5px var(--brand-blue);
|
||||
}
|
||||
|
||||
/* Specific color override for the Spotlight card bullets */
|
||||
.spotlight .service-benefits li::before {
|
||||
color: #ffd700;
|
||||
text-shadow: 0 0 5px #ffd700;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@page "/shop"
|
||||
|
||||
<h3>Shop</h3>
|
||||
|
||||
@code {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
body {
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
@page "/terms"
|
||||
|
||||
<div class="terms-page">
|
||||
<div class="terms-container">
|
||||
<h1>Terms and Conditions</h1>
|
||||
<p class="last-updated">Last Updated: May 2026</p>
|
||||
|
||||
<p>Welcome to Lite Charms. These terms govern your use of our website and services. By using our site or purchasing our services, you agree to these terms in full. All transactions and communications with Lite Charms (PTY) LTD will be conducted in <strong>English</strong>.</p>
|
||||
|
||||
<h3>1. Governing Law and Statutory Compliance</h3>
|
||||
<p>These terms are governed by and interpreted in accordance with the laws of the Republic of South Africa. We explicitly adhere to:</p>
|
||||
<ul>
|
||||
<li><strong>The Electronic Communications and Transactions Act 25 of 2002 (ECTA):</strong> Governing all electronic data, communications, and online transactions.</li>
|
||||
<li><strong>The Consumer Protection Act 68 of 2008 (CPA):</strong> Protecting your rights as a consumer in the South African marketplace.</li>
|
||||
</ul>
|
||||
<p>Any disputes arising from these terms will be subject to the exclusive jurisdiction of the South African courts.</p>
|
||||
|
||||
<h3>2. Services, Quotes, and Market Fluctuations</h3>
|
||||
<p><strong>Best Effort Quotes:</strong> All quotes provided are "best effort" estimates. Prices are subject to market fluctuations and third-party provider changes. Upfront Fees: We do not hold deposits. However, certain services require a 50% upfront payment before work commences. Due correspondence will be provided for these requirements before any payment is requested.</p>
|
||||
|
||||
<h3>3. Support and Service Level Agreement (SLA)</h3>
|
||||
<p><strong>Standard Support:</strong> We offer a free 3-month support period starting from the date of project delivery. Response Times: For Server and Cloud builds, we maintain a 73-hour SLA during the free support period.</p>
|
||||
<p><strong>Expiration:</strong> Unless a separate, paid SLA is purchased outside of the standard offerings on this site, all support obligations expire exactly 3 months after delivery. Service Hours: Our standard operating hours are Monday to Friday, 9:00 AM to 5:00 PM. Requests made on weekends or public holidays will attract an hourly rate quoted on call.</p>
|
||||
|
||||
<h3>4. Electronic Payments and Transaction Security</h3>
|
||||
<p>In accordance with <strong>ECTA</strong>, all payments must be made via our approved electronic channels. We take reasonable and industry-standard technical measures to secure your payment information. However, users are responsible for ensuring their own hardware and network security during transactions.</p>
|
||||
|
||||
<h3>5. Refunds and Cancellations</h3>
|
||||
<p><strong>Work-Based Refunds:</strong> Refunds are granted based on whether the technical work has been initiated or completed, and strictly within the parameters allowed by the <strong>Consumer Protection Act</strong> regarding professional digital services. Due to the nature of custom code and server configurations, refunds may be limited once resources are provisioned.</p>
|
||||
|
||||
<h3>6. Limitation of Liability and Warranties</h3>
|
||||
<p><strong>No Warranties:</strong> To the extent permitted by South African law, we provide our code and services "as is" and offer no warranties, express or implied. Usage Risks: Lite Charms (PTY) LTD cannot be held liable for any "breakages," data loss, or system downtime ensuing from the use of our code or configurations.</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,48 @@
|
||||
/* --- Terms & Conditions Styling --- */
|
||||
.terms-page {
|
||||
width: 100%;
|
||||
background-color: var(--bar-bg);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.terms-container {
|
||||
max-width: 900px; /* Limits width for readability */
|
||||
width: 90%;
|
||||
padding: 60px 0;
|
||||
line-height: 1.8; /* Improves readability */
|
||||
color: var(--text-white);
|
||||
}
|
||||
|
||||
.terms-container h1 {
|
||||
font-size: 2.5rem;
|
||||
color: var(--brand-blue);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.terms-container .last-updated {
|
||||
color: var(--payoff-color);
|
||||
font-style: italic;
|
||||
margin-bottom: 40px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.terms-container h3 {
|
||||
color: var(--brand-blue);
|
||||
font-size: 1.4rem;
|
||||
margin-top: 35px;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 1px solid rgba(0, 150, 199, 0.2);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.terms-container p {
|
||||
margin-bottom: 20px;
|
||||
text-align: justify; /* Optional: gives a formal look */
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.terms-container strong {
|
||||
color: var(--payoff-color);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
</Router>
|
||||
@@ -0,0 +1,10 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using Components
|
||||
@using Layout
|
||||
@@ -0,0 +1,56 @@
|
||||
using LiteCharms.Extensions;
|
||||
using Shop.Components;
|
||||
using static LiteCharms.Abstractions.Constants;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
|
||||
builder.AddMonitoring();
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddBlazoredToast();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
|
||||
builder.Services.AddMediator();
|
||||
builder.Services.AddEmailServiceBus();
|
||||
builder.Services.AddSalesServiceBus();
|
||||
builder.Services.AddGeneralServiceBus();
|
||||
builder.Services.AddEmailServices(builder.Configuration);
|
||||
builder.Services.AddLeadGeneratorDatabase(builder.Configuration);
|
||||
builder.Services.AddQuartzSchedulerClient(LeadGeneratorSchedulerName, LeadGeneratorSchedulerInstanceId, builder.Configuration);
|
||||
|
||||
builder.Services.AddPostgresHealtchCheck();
|
||||
builder.Services.AddQuartzHealtchCheck();
|
||||
builder.Services.AddHealthChecksSupport(builder.Configuration);
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
var schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>();
|
||||
var scheduler = await schedulerFactory.GetScheduler(LeadGeneratorSchedulerName);
|
||||
|
||||
if (!scheduler!.IsStarted)
|
||||
await scheduler.Start();
|
||||
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHealthChecks("/health", new HealthCheckOptions
|
||||
{
|
||||
ResponseWriter = HealthChecks.UI.Client.UIResponseWriter.WriteHealthCheckUIResponse
|
||||
});
|
||||
|
||||
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapStaticAssets();
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode();
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5226",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7007;http://localhost:5226",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<SignAssembly>True</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\LiteCharms.snk</AssemblyOriginatorKeyFile>
|
||||
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||
<UserSecretsId>bcb3ab6a-28e8-45c8-a7e0-8daae750227c</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- file nesting -->
|
||||
<ItemGroup>
|
||||
<ProjectCapability Include="ConfigurableFileNesting" />
|
||||
<ProjectCapability Include="ConfigurableFileNestingFeatureEnabled" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="LiteCharms.Extensions" Version="1.11.0" />
|
||||
<PackageReference Include="LiteCharms.Features" Version="1.11.0" />
|
||||
<PackageReference Include="LiteCharms.Models" Version="1.11.0" />
|
||||
<PackageReference Include="Polly" Version="8.6.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- UI -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ANM.Blazored.Toast" Version="0.1.1" />
|
||||
|
||||
<!-- Global Usings -->
|
||||
<Using Include="Blazored.Toast.Services" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- CQRS -->
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Mediator.SourceGenerator" Version="3.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<!-- Global Usings -->
|
||||
<Using Include="FluentResults" />
|
||||
<Using Include="Mediator" />
|
||||
<Using Include="Quartz" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Health Checks -->
|
||||
<ItemGroup>
|
||||
<Using Include="Microsoft.AspNetCore.Diagnostics.HealthChecks" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Shared Global Usings -->
|
||||
<ItemGroup>
|
||||
<Using Include="Blazored.Toast" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"Email": {
|
||||
"Credentials": {
|
||||
"Username": "shop@litecharms.co.za"
|
||||
},
|
||||
"Port": 465,
|
||||
"Host": "mail.litecharms.co.za",
|
||||
"UseSsl": true
|
||||
},
|
||||
"Monitoring": {
|
||||
"ApiKey": "",
|
||||
"Address": "http://aspire-dashboard-service.aspire.svc.cluster.local:18889",
|
||||
"ServiceName": "LiteCharms.LeadGenerator"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -0,0 +1,578 @@
|
||||
/* --- 1. Variables & Global Reset --- */
|
||||
:root {
|
||||
--bar-bg: #001219;
|
||||
--text-white: #ffffff;
|
||||
--brand-blue: #0096c7;
|
||||
--hover-blue: #00b4d8;
|
||||
--payoff-color: #90e0ef;
|
||||
--header-height: 92px;
|
||||
}
|
||||
|
||||
h1, .btn-calltoaction-blue, .btn-calltoaction-white, .nav-btn {
|
||||
/* Prevents the glowing outline when focused */
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
background-color: var(--bar-bg);
|
||||
color: var(--text-white);
|
||||
overflow-x: hidden;
|
||||
/* FIX: Ensures the background covers the full viewport height */
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* --- 2. Layout: Top Bar --- */
|
||||
.top-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: var(--header-height);
|
||||
background-color: var(--bar-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 4rem;
|
||||
z-index: 1000;
|
||||
border-bottom: 1px solid rgba(144, 224, 239, 0.15);
|
||||
}
|
||||
|
||||
/* --- 3. Branding & Logo --- */
|
||||
.brand {
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.brand-icon, .brand-mark {
|
||||
height: 70px;
|
||||
width: auto;
|
||||
flex-shrink: 0;
|
||||
filter: drop-shadow(0 0 12px rgba(0, 180, 216, 0.5));
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.brand:hover .brand-icon, .brand:hover .brand-mark {
|
||||
transform: rotate(5deg) scale(1.05);
|
||||
}
|
||||
|
||||
.text-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-left: 16px;
|
||||
margin-left: 8px;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.brand-main {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 800;
|
||||
color: var(--text-white);
|
||||
letter-spacing: -0.5px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.brand-accent {
|
||||
color: var(--brand-blue);
|
||||
}
|
||||
|
||||
.payoff-line {
|
||||
font-size: 0.82rem;
|
||||
color: var(--payoff-color);
|
||||
margin-top: 2px;
|
||||
letter-spacing: 0.3px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* --- 4. Navigation Links --- */
|
||||
.nav-links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--text-white);
|
||||
text-decoration: none;
|
||||
margin: 0 18px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-link::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
bottom: -5px;
|
||||
left: 0;
|
||||
background-color: var(--brand-blue);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--hover-blue);
|
||||
}
|
||||
|
||||
.nav-link:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* --- 5. Actions & Buttons --- */
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.phone-number {
|
||||
margin-right: 25px;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* --- Header Actions Refinement --- */
|
||||
.contact-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.contact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-white);
|
||||
gap: 8px; /* Space between icon and text */
|
||||
}
|
||||
|
||||
.contact-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--brand-blue);
|
||||
}
|
||||
|
||||
.blue-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: var(--brand-blue);
|
||||
border-radius: 50%;
|
||||
margin: 0 15px;
|
||||
display: inline-block;
|
||||
box-shadow: 0 0 8px rgba(0, 150, 199, 0.6);
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background-color: var(--brand-blue);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 24px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(0, 150, 199, 0.2);
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.btn-login:hover {
|
||||
background-color: var(--hover-blue);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 180, 216, 0.3);
|
||||
}
|
||||
|
||||
.btn-calltoaction-white {
|
||||
background-color: white;
|
||||
color: var(--brand-blue);
|
||||
border: none;
|
||||
padding: 10px 24px;
|
||||
border-radius: 20px;
|
||||
font-weight: 400;
|
||||
font-size: 1.23rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(0, 150, 199, 0.2);
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
.btn-calltoaction-white:hover {
|
||||
background-color: lightblue;
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 180, 216, 0.3);
|
||||
}
|
||||
|
||||
.btn-calltoaction-blue {
|
||||
background-color: var(--brand-blue);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 24px;
|
||||
border-radius: 20px;
|
||||
font-weight: 400;
|
||||
font-size: 1.23rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(0, 150, 199, 0.2);
|
||||
height: 55px;
|
||||
}
|
||||
|
||||
.btn-calltoaction-blue:hover {
|
||||
background-color: var(--brand-blue);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 180, 216, 0.3);
|
||||
}
|
||||
|
||||
/* --- 6. Responsive Logic --- */
|
||||
.hamburger, .mobile-only {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.top-bar {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.phone-number {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 991px) {
|
||||
.desktop-only {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.mobile-only {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
display: block;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
z-index: 1200;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bar {
|
||||
width: 25px;
|
||||
height: 3px;
|
||||
background-color: var(--text-white);
|
||||
position: relative;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.bar::before, .bar::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 25px;
|
||||
height: 3px;
|
||||
background-color: var(--text-white);
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.bar::before {
|
||||
top: -8px;
|
||||
}
|
||||
|
||||
.bar::after {
|
||||
bottom: 8px;
|
||||
}
|
||||
|
||||
.bar.animate {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.bar.animate::before {
|
||||
transform: translateY(8px) rotate(45deg);
|
||||
}
|
||||
|
||||
.bar.animate::after {
|
||||
transform: translateY(-8px) rotate(-45deg);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: -100%;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: var(--bar-bg);
|
||||
transition: right 0.4s ease-in-out;
|
||||
z-index: 1100;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.nav-links.open {
|
||||
right: 0;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
font-size: 1.5rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- 7. Page Structure (THE GAP FIX) --- */
|
||||
.page-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* Flex: 1 tells the wrapper to grow and fill the body */
|
||||
flex: 1 0 auto;
|
||||
padding-top: var(--header-height);
|
||||
padding-bottom: 50px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--bar-bg);
|
||||
}
|
||||
|
||||
main {
|
||||
/* Flex: 1 here tells the main content area to expand and push the footer to the bottom */
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center; /* This will center your "Lite Charms" logo vertically in the middle */
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/*.bottom-bar {
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
flex-shrink: 0;*/ /* Ensures footer is never squeezed */
|
||||
/*background-color: var(--bar-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 4rem;
|
||||
font-size: 0.75rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
border-top: 1px solid rgba(144, 224, 239, 0.1);
|
||||
box-sizing: border-box;
|
||||
}*/
|
||||
.bottom-bar {
|
||||
/* NEW: Sticky/Fixed Logic */
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
background-color: var(--bar-bg);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 4rem;
|
||||
font-size: 0.75rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
/* Keep that subtle border but move it to the top since it's at the bottom of the screen */
|
||||
border-top: 1px solid rgba(144, 224, 239, 0.1);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Ensure this exists in your CSS file */
|
||||
@media (max-width: 991px) {
|
||||
.nav-links.open {
|
||||
right: 0 !important;
|
||||
visibility: visible !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-link {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.footer-link:hover {
|
||||
color: var(--brand-blue);
|
||||
}
|
||||
|
||||
/* --- 8. Highlights Section --- */
|
||||
.highlights-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40px;
|
||||
margin-top: 60px;
|
||||
width: 100%;
|
||||
max-width: 1100px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.highlight-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.highlight-item:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.highlight-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
/* Using your brand blue for consistency */
|
||||
color: var(--brand-blue);
|
||||
}
|
||||
|
||||
/* Specific colors for icons to match your reference image */
|
||||
.icon-blue {
|
||||
color: #4dabff;
|
||||
}
|
||||
|
||||
.icon-green {
|
||||
color: #4ade80;
|
||||
}
|
||||
|
||||
.icon-gold {
|
||||
color: #fbbf24;
|
||||
}
|
||||
|
||||
.highlight-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 8px;
|
||||
color: var(--text-white);
|
||||
}
|
||||
|
||||
.highlight-desc {
|
||||
font-size: 0.9rem;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Responsive fix for mobile */
|
||||
@media (max-width: 768px) {
|
||||
.highlights-container {
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Custom Scrollbar Branding --- */
|
||||
|
||||
/* 1. The width of the entire scrollbar */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px; /* Thin profile */
|
||||
height: 8px; /* Horizontal scrollbar height */
|
||||
}
|
||||
|
||||
/* 2. The track (background) of the scrollbar */
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--bar-bg);
|
||||
border-left: 1px solid rgba(144, 224, 239, 0.05);
|
||||
}
|
||||
|
||||
/* 3. The draggable thumb */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--brand-blue);
|
||||
border-radius: 10px; /* Rounded edges for a modern look */
|
||||
border: 2px solid var(--bar-bg); /* Creates a "padding" effect around the thumb */
|
||||
}
|
||||
|
||||
/* 4. Thumb hover state */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--hover-blue);
|
||||
}
|
||||
|
||||
/* Firefox Support (Standard property) */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--brand-blue) var(--bar-bg);
|
||||
}
|
||||
|
||||
/* --- Cookie Consent Banner --- */
|
||||
.cookie-banner {
|
||||
position: fixed;
|
||||
bottom: 80px; /* Sits just above your sticky footer */
|
||||
right: 20px;
|
||||
width: 380px;
|
||||
background-color: var(--bar-bg);
|
||||
border: 1px solid rgba(144, 224, 239, 0.2);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
z-index: 2000;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
display: none; /* Controlled by Blazor logic */
|
||||
}
|
||||
|
||||
.cookie-banner.show {
|
||||
display: block;
|
||||
animation: slideUp 0.4s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.cookie-text {
|
||||
font-size: 0.85rem;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.cookie-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-cookie-accept {
|
||||
flex: 1;
|
||||
background-color: var(--brand-blue);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-cookie-reject {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
color: var(--text-white);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,195 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: litecharms-shop-uat
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: shop-config
|
||||
namespace: litecharms-shop-uat
|
||||
data:
|
||||
ASPNETCORE_ENVIRONMENT: "Development"
|
||||
ASPNETCORE_URLS: "http://0.0.0.0:8080"
|
||||
Monitoring__Address: "http://aspire-dashboard-service.aspire.svc.cluster.local:18889"
|
||||
Monitoring__ServiceName: "LiteCharms.LeadGenerator.Uat"
|
||||
Email__Credentials__Username: "shop@litecharms.co.za"
|
||||
Email__Host: "mail.litecharms.co.za"
|
||||
Email__Port: "465"
|
||||
Email__UseSsl: "true"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: shop-secrets
|
||||
namespace: litecharms-shop-uat
|
||||
type: Opaque
|
||||
data:
|
||||
connection-string: SG9zdD0xOTIuMTY4LjEuMTcwO0RhdGFiYXNlPWxlYWRnZW5lcmF0b3ItZGV2O1VzZXJuYW1lPWxlYWRnZW5lcmF0b3I7UGFzc3dvcmQ9S2VLNDRsczRQWHBuYms7UGVyc2lzdCBTZWN1cml0eSBJbmZvPVRydWU=
|
||||
discord-webhook: aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTUwMDIzMzEyOTYwNzAzNjk3MC9KYzc5endwMjlxYWpLbmoyYkR3cm5GR0RJci11ZGIyV2JIUDZTYjdpT0hCTWpQSUY3Vkw5eUVHTkJUSXpSOVVWVzI0bQ==
|
||||
aspire-apikey: bWMzRzYzSzJqNVpPRXNpMEFqTW9qTFRYbTFLRVpGY3R6SUlqU3dEaVRHdXQ4cUdTa1B1V3d4R1AxUmJzY0pVbw==
|
||||
email-password: JFpTLWVJQGlYbTVNUCRhfg==
|
||||
quartz-store: SG9zdD0xOTIuMTY4LjEuMTcwO0RhdGFiYXNlPXNjaGVkdWxlci1kZXY7VXNlcm5hbWU9c2NoZWR1bGVyLWRldi11c2VyO1Bhc3N3b3JkPWtWVm1vV0tKM3h6Z1FYO1BlcnNpc3QgU2VjdXJpdHkgSW5mbz1UcnVl
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: shop-data-pvc
|
||||
namespace: litecharms-shop-uat
|
||||
spec:
|
||||
accessModes: ["ReadWriteMany"]
|
||||
storageClassName: nfs-storage
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: litecharms-leadgenerator
|
||||
namespace: litecharms-shop-uat
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: leadgenerator
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: leadgenerator
|
||||
spec:
|
||||
affinity:
|
||||
nodeAffinity:
|
||||
requiredDuringSchedulingIgnoredDuringExecution:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: DoesNotExist
|
||||
containers:
|
||||
- name: leadgenerator
|
||||
image: nexus.khongisa.co.za/litecharms-leadgenerator:latest
|
||||
resources:
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: shop-config
|
||||
env:
|
||||
- name: Email__Credentials__Username
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: shop-config
|
||||
key: Email__Credentials__Username
|
||||
- name: Email__Credentials__Password
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: shop-secrets
|
||||
key: email-password
|
||||
- name: Email__Host
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: shop-config
|
||||
key: Email__Host
|
||||
- name: Email__Port
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: shop-config
|
||||
key: Email__Port
|
||||
- name: Email__UseSsl
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: shop-config
|
||||
key: Email__UseSsl
|
||||
- name: ConnectionStrings__PostgresScheduler
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: shop-secrets
|
||||
key: quartz-store
|
||||
- name: ConnectionStrings__PostgresLeadGenerator
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: shop-secrets
|
||||
key: connection-string
|
||||
- name: ConnectionStrings__DiscordLeadGenerator
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: shop-secrets
|
||||
key: discord-webhook
|
||||
- name: Monitoring__Address
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: shop-config
|
||||
key: Monitoring__Address
|
||||
- name: Monitoring__ServiceName
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: shop-config
|
||||
key: Monitoring__ServiceName
|
||||
- name: Monitoring__ApiKey
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: shop-secrets
|
||||
key: aspire-apikey
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /app/wwwroot/content
|
||||
resources:
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: shop-data-pvc
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: leadgenerator-service
|
||||
namespace: litecharms-shop-uat
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: leadgenerator
|
||||
ports:
|
||||
- name: http
|
||||
protocol: TCP
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: shop-web-secure
|
||||
namespace: litecharms-shop-uat
|
||||
spec:
|
||||
entryPoints:
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`shop.uat.khongisa.co.za`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: leadgenerator-service
|
||||
port: 80
|
||||
sticky:
|
||||
cookie:
|
||||
name: "lp-sticky-session"
|
||||
httpOnly: true
|
||||
secure: true
|
||||
tls: {}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="KhongisaNexus" value="https://nexus.khongisa.co.za/repository/nuget-group/index.json" />
|
||||
</packageSources>
|
||||
|
||||
<packageSourceCredentials>
|
||||
<KhongisaNexus>
|
||||
<add key="Username" value="api-key" />
|
||||
<add key="ClearTextPassword" value="4a285231-5a49-334f-a216-7c2e63418e5c" />
|
||||
</KhongisaNexus>
|
||||
</packageSourceCredentials>
|
||||
</configuration>
|
||||
Reference in New Issue
Block a user