Compare commits

..

2 Commits

Author SHA1 Message Date
917fef3f71 fix: planos
All checks were successful
BCards Multi-Tenant Deployment Pipeline / Run Tests (push) Successful in 9s
BCards Multi-Tenant Deployment Pipeline / PR Validation (push) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Build and Push Image (push) Successful in 13m1s
BCards Multi-Tenant Deployment Pipeline / Deploy to Release Swarm (ARM) (push) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deploy bcards.site (push) Successful in 1m4s
BCards Multi-Tenant Deployment Pipeline / Deploy spicylinks.site (push) Successful in 1m5s
BCards Multi-Tenant Deployment Pipeline / Deploy luslinks.site (push) Successful in 1m3s
BCards Multi-Tenant Deployment Pipeline / Cleanup Old Resources (push) Has been skipped
BCards Multi-Tenant Deployment Pipeline / Deployment Summary (push) Successful in 1s
2026-04-30 19:51:05 -03:00
be4e766f9b fix: aparencia diferenste para os tenants 2026-04-30 19:35:32 -03:00
12 changed files with 395 additions and 124 deletions

View File

@ -36,7 +36,21 @@
"Bash(lsof:*)", "Bash(lsof:*)",
"Bash(dotnet run:*)", "Bash(dotnet run:*)",
"Bash(dotnet user-secrets:*)", "Bash(dotnet user-secrets:*)",
"Bash(xargs grep:*)" "Bash(xargs grep:*)",
"mcp__stripe__list_products",
"mcp__stripe__list_prices",
"mcp__stripe__get_stripe_account_info",
"mcp__stripe__search_stripe_resources",
"Bash(docker exec:*)",
"mcp__stripe__list_subscriptions",
"mcp__stripe__create_product",
"mcp__stripe__create_price",
"Bash(git push:*)",
"mcp__stripe__stripe_api_execute",
"mcp__stripe__stripe_api_search",
"Bash(ctx csharp:*)",
"Bash(ctx auto:*)",
"Bash(git log:*)"
] ]
}, },
"enableAllProjectMcpServers": false "enableAllProjectMcpServers": false

View File

@ -254,7 +254,10 @@ jobs:
"CtaDescription": "Junte-se a milhares de profissionais que já têm sua presença digital organizada no BCards.", "CtaDescription": "Junte-se a milhares de profissionais que já têm sua presença digital organizada no BCards.",
"CtaButtonText": "Criar Minha Página Grátis", "CtaButtonText": "Criar Minha Página Grátis",
"MetaKeywords": "cartão digital, página de links, bio links, linktree brasil, página profissional, corretor, advogado, médico, consultor", "MetaKeywords": "cartão digital, página de links, bio links, linktree brasil, página profissional, corretor, advogado, médico, consultor",
"FooterTagline": "Sua presença digital profissional, simplificada." "FooterTagline": "Sua presença digital profissional, simplificada.",
"HeroGradient": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
"PrimaryColor": "#667eea",
"PrimaryColorDark": "#5a6fd6"
}, },
"Support": { "Support": {
"TelegramUrl": "https://t.me/jobmakerbr", "TelegramUrl": "https://t.me/jobmakerbr",
@ -399,7 +402,30 @@ jobs:
"CtaDescription": "Milhares de criadoras já centralizam seus links e aumentam suas conversões com o SpicyLinks.", "CtaDescription": "Milhares de criadoras já centralizam seus links e aumentam suas conversões com o SpicyLinks.",
"CtaButtonText": "Criar Minha Bio", "CtaButtonText": "Criar Minha Bio",
"MetaKeywords": "bio links criadora, creator bio, linktree conteudo adulto, links onlyfans, bio instagram criadora", "MetaKeywords": "bio links criadora, creator bio, linktree conteudo adulto, links onlyfans, bio instagram criadora",
"FooterTagline": "Seu conteúdo, sua identidade." "FooterTagline": "Seu conteúdo, sua identidade.",
"HeroGradient": "linear-gradient(135deg, #ff416c 0%, #c0392b 100%)",
"PrimaryColor": "#e63946",
"PrimaryColorDark": "#c1121f",
"DefaultCategories": [
{ "Icon": "📸", "Name": "Modelos", "Slug": "modelos", "Description": "Modelos e criadores de conteúdo visual", "SeoKeywords": [ "modelo", "fotografia", "conteúdo", "criadora" ] },
{ "Icon": "⭐", "Name": "Influencers", "Slug": "influencers","Description": "Influencers e personalidades digitais", "SeoKeywords": [ "influencer", "digital", "social media" ] },
{ "Icon": "💪", "Name": "Fitness", "Slug": "fitness", "Description": "Criadores de conteúdo fitness e lifestyle", "SeoKeywords": [ "fitness", "academia", "saúde", "corpo" ] },
{ "Icon": "🎨", "Name": "Arte", "Slug": "arte", "Description": "Artistas e criadores de conteúdo visual", "SeoKeywords": [ "arte", "ilustração", "design", "criativo" ] },
{ "Icon": "🎵", "Name": "Música", "Slug": "musica", "Description": "Músicos e cantores independentes", "SeoKeywords": [ "música", "cantor", "artista", "show" ] },
{ "Icon": "🎮", "Name": "Gaming", "Slug": "gaming", "Description": "Streamers e criadores de conteúdo gamer", "SeoKeywords": [ "gaming", "streamer", "games", "twitch" ] },
{ "Icon": "🦸", "Name": "Cosplay", "Slug": "cosplay", "Description": "Cosplayers e criadores de fantasia", "SeoKeywords": [ "cosplay", "anime", "fantasia", "cosplayer" ] },
{ "Icon": "💋", "Name": "Lifestyle", "Slug": "lifestyle", "Description": "Criadores de conteúdo lifestyle e entretenimento", "SeoKeywords": [ "lifestyle", "entretenimento", "diversão" ] }
],
"AllowedLinkTypes": [
{ "Icon": "fas fa-globe", "Label": "🌐 Site Geral", "Prefix": "https://", "Placeholder": "exemplo.com", "Instructions": "Digite o domínio e caminho", "Color": "bg-primary" },
{ "Icon": "fas fa-envelope", "Label": "✉️ Email", "Prefix": "mailto:", "Placeholder": "seuemail@exemplo.com", "Instructions": "Digite apenas o email", "Color": "bg-success" },
{ "Icon": "fas fa-phone", "Label": "📞 Telefone", "Prefix": "tel:", "Placeholder": "5511999999999", "Instructions": "Número com código do país", "Color": "bg-success" },
{ "Icon": "fab fa-instagram", "Label": "📸 Instagram", "Prefix": "https://instagram.com/","Placeholder": "seu.usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-danger" },
{ "Icon": "fab fa-twitter", "Label": "🐦 Twitter/X", "Prefix": "https://x.com/", "Placeholder": "seu_usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-dark" },
{ "Icon": "fab fa-tiktok", "Label": "🎵 TikTok", "Prefix": "https://tiktok.com/@", "Placeholder": "seu.usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-dark" },
{ "Icon": "fas fa-shopping-cart","Label": "🛒 Lista de Desejos","Prefix": "https://", "Placeholder": "wishlist.com/...", "Instructions": "Link para lista de desejos", "Color": "bg-warning" },
{ "Icon": "fas fa-heart", "Label": "❤️ Assinatura", "Prefix": "https://", "Placeholder": "plataforma.com/...", "Instructions": "Link para plataforma paga", "Color": "bg-danger" }
]
}, },
"Support": { "Support": {
"TelegramUrl": "https://t.me/jobmakerbr", "TelegramUrl": "https://t.me/jobmakerbr",
@ -544,7 +570,22 @@ jobs:
"CtaDescription": "Líderes de toda denominação já usam o LusLinks para alcançar mais pessoas com sua mensagem de fé.", "CtaDescription": "Líderes de toda denominação já usam o LusLinks para alcançar mais pessoas com sua mensagem de fé.",
"CtaButtonText": "Criar Minha Bio de Fé", "CtaButtonText": "Criar Minha Bio de Fé",
"MetaKeywords": "bio links pastor, página ministério, linktree cristão, links religiosos, página iglesia, bio pastor, links igreja", "MetaKeywords": "bio links pastor, página ministério, linktree cristão, links religiosos, página iglesia, bio pastor, links igreja",
"FooterTagline": "Conectando fé e comunidade." "FooterTagline": "Conectando fé e comunidade.",
"HeroGradient": "linear-gradient(135deg, #5b9bd5 0%, #1a5276 100%)",
"PrimaryColor": "#2471a3",
"PrimaryColorDark": "#1a5276",
"AllowedLinkTypes": [
{ "Icon": "fas fa-globe", "Label": "🌐 Site / Ministério", "Prefix": "https://", "Placeholder": "ministerio.com.br", "Instructions": "Digite o domínio do site", "Color": "bg-primary" },
{ "Icon": "fas fa-envelope", "Label": "✉️ Email", "Prefix": "mailto:", "Placeholder": "contato@ministerio.com", "Instructions": "Digite apenas o email", "Color": "bg-success" },
{ "Icon": "fas fa-phone", "Label": "📞 Telefone / WhatsApp", "Prefix": "tel:", "Placeholder": "5511999999999", "Instructions": "Número com código do país", "Color": "bg-success" },
{ "Icon": "fab fa-youtube", "Label": "📺 YouTube", "Prefix": "https://youtube.com/", "Placeholder": "@canal ou c/CANAL", "Instructions": "Digite o canal ou @usuário", "Color": "bg-danger" },
{ "Icon": "fab fa-instagram", "Label": "📸 Instagram", "Prefix": "https://instagram.com/", "Placeholder": "seu.usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-danger" },
{ "Icon": "fas fa-book", "Label": "📖 Estudo / Série", "Prefix": "https://", "Placeholder": "link-do-estudo.com", "Instructions": "Link para estudo bíblico ou série", "Color": "bg-info" },
{ "Icon": "fas fa-calendar", "Label": "📅 Agenda / Eventos", "Prefix": "https://", "Placeholder": "calendly.com/seunome", "Instructions": "Link para agenda ou evento", "Color": "bg-warning" },
{ "Icon": "fas fa-donate", "Label": "🙏 Dízimos / Ofertas", "Prefix": "https://", "Placeholder": "pix.com.br/ministerio", "Instructions": "Link para doações ou dízimos", "Color": "bg-success" },
{ "Icon": "fas fa-map-marker-alt","Label": "📍 Localização", "Prefix": "https://maps.google.com/?q=", "VisualPrefix": "📍 Maps:", "Placeholder": "Rua da Igreja, 123", "Instructions": "Endereço da igreja/ministério", "Color": "bg-warning" },
{ "Icon": "fas fa-download", "Label": "⬇️ Material / Apostila","Prefix": "https://", "Placeholder": "drive.google.com/...", "Instructions": "Link para download de material", "Color": "bg-secondary" }
]
}, },
"Support": { "Support": {
"TelegramUrl": "https://t.me/jobmakerbr", "TelegramUrl": "https://t.me/jobmakerbr",

View File

@ -243,4 +243,46 @@ if (page.Status == PageStatus.Creating || page.Status == PageStatus.Rejected)
} }
``` ```
This architecture supports a production-ready SaaS application with complex business rules, payment integration, and content moderation workflows. This architecture supports a production-ready SaaS application with complex business rules, payment integration, and content moderation workflows.
# Project instructions
## ctx — use before reading files
This project has `ctx` available in PATH. Use it to understand the codebase **before** reading files directly. It produces compact markdown summaries that cost far fewer tokens than raw file content.
### When to use
**Always use `ctx` first** when you need to:
- Understand project structure → `ctx csharp project` or `ctx react project`
- Understand a file's structure → `ctx csharp outline <file>` or `ctx react outline <file>`
- Check build errors → `ctx csharp errors` or `ctx react errors`
- Understand git state → `ctx git`
- Detect what stack this project uses → `ctx auto detect`
### Workflow
1. **Start of session:** run `ctx auto detect` to see what's here, then `ctx <stack> project` for an overview.
2. **Before reading a file:** run `ctx <stack> outline <file>` first. Only read the full file if the outline isn't enough (e.g., you need to see method body logic).
3. **After making changes:** run `ctx <stack> errors` instead of `dotnet build` or `tsc` — the output is pre-filtered to only relevant diagnostics.
4. **Before committing:** run `ctx git` for a compact diff summary.
### Available commands
```
ctx auto detect # detect stack(s) in current directory
ctx auto project # run project summary for all detected stacks
ctx csharp project # .NET solution overview (projects, refs, packages)
ctx csharp outline <file.cs> # file structure without method bodies
ctx csharp errors # filtered dotnet build output (errors + top warnings)
ctx git # branch, status, recent commits, diff summary
```
### Important
- `ctx` output is a **summary**, not the full picture. If you need implementation details (method bodies, exact logic, specific lines), read the file directly.
- Do not run `ctx` commands that don't match the project stack. Use `ctx auto detect` if unsure.
- `ctx csharp errors` assumes `dotnet restore` was already run. If you get restore errors, run `dotnet restore` first, then `ctx csharp errors`.

BIN
saida.md Normal file

Binary file not shown.

View File

@ -69,34 +69,35 @@ if (isDevelopment)
if (!string.IsNullOrEmpty(openSearchUrl)) if (!string.IsNullOrEmpty(openSearchUrl))
{ {
var indexFormat = "b-cards-dev-{0:yyyy-MM}"; var indexFormat = "b-cards-dev-{0:yyyy-MM}";
Console.WriteLine($"[OPENSEARCH DEV] Configurando sink → {openSearchUrl}");
try // TODO: após confirmar que conecta, restaurar try/catch silencioso (opcional)
loggerConfig.WriteTo.Async(a => a.OpenSearch(new OpenSearchSinkOptions(new Uri(openSearchUrl))
{ {
loggerConfig.WriteTo.Async(a => a.OpenSearch(new OpenSearchSinkOptions(new Uri(openSearchUrl)) IndexFormat = indexFormat,
AutoRegisterTemplate = false,
ModifyConnectionSettings = conn => conn
.RequestTimeout(TimeSpan.FromSeconds(5))
.PingTimeout(TimeSpan.FromSeconds(3)),
MinimumLogEventLevel = LogEventLevel.Debug,
EmitEventFailure = EmitEventFailureHandling.WriteToSelfLog,
RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway,
BatchPostingLimit = 10,
Period = TimeSpan.FromSeconds(2),
TemplateCustomSettings = new Dictionary<string, string>
{ {
IndexFormat = indexFormat, {"number_of_shards", "1"},
AutoRegisterTemplate = false, // Não faz GET / no startup {"number_of_replicas", "0"}
ModifyConnectionSettings = conn => conn }
.RequestTimeout(TimeSpan.FromSeconds(5)) }),
.PingTimeout(TimeSpan.FromSeconds(3)), bufferSize: 10000,
MinimumLogEventLevel = LogEventLevel.Debug, blockWhenFull: false);
EmitEventFailure = EmitEventFailureHandling.WriteToSelfLog,
RegisterTemplateFailure = RegisterTemplateRecovery.IndexAnyway, Console.WriteLine($"[OPENSEARCH DEV] Sink registrado. Erros de envio aparecem como [SERILOG SELF] no console.");
BatchPostingLimit = 10, }
Period = TimeSpan.FromSeconds(2), else
TemplateCustomSettings = new Dictionary<string, string> {
{ Console.WriteLine("[OPENSEARCH DEV] OpenSearchUrl não configurado — sem sink OpenSearch.");
{"number_of_shards", "1"},
{"number_of_replicas", "0"}
}
}),
bufferSize: 10000,
blockWhenFull: false);
}
catch (Exception)
{
// Falha silenciosa - logs continuam no console e arquivo
}
} }
} }
else else
@ -151,9 +152,10 @@ else
Period = TimeSpan.FromSeconds(5), Period = TimeSpan.FromSeconds(5),
}), bufferSize: 10000, blockWhenFull: false); }), bufferSize: 10000, blockWhenFull: false);
} }
catch (Exception) catch (Exception ex)
{ {
// Falha silenciosa em produção - logs continuam no console/arquivo // OpenSearch é opcional — app continua com console/arquivo
Console.WriteLine($"[OPENSEARCH PROD] Falha ao configurar sink → {openSearchUrl}: {ex.Message}");
} }
} }
} }

View File

@ -7,29 +7,75 @@
Layout = "_Layout"; Layout = "_Layout";
} }
<div class="container py-5"> <div class="container-fluid px-3 px-lg-5 py-5">
<div class="text-center mb-5"> <div class="text-center mb-5">
<h1 class="display-5 fw-bold mb-3">Escolha o plano ideal para você</h1> <h1 class="display-5 fw-bold mb-3">Escolha o plano ideal para você</h1>
<p class="lead text-muted">Comece grátis e faça upgrade quando precisar de mais recursos</p> <p class="lead text-muted">Comece grátis e faça upgrade quando precisar de mais recursos</p>
<!-- Toggle Mensal/Anual --> <!-- Toggle Mensal/Anual -->
<div class="d-flex justify-content-center mb-4"> <div class="d-flex justify-content-center mb-4 mt-3">
<div class="btn-group" role="group" aria-label="Período de cobrança"> <div class="pricing-toggle-wrapper">
<input type="radio" class="btn-check" name="billingPeriod" id="monthly" autocomplete="off" checked> <span class="pricing-savings-chip">🎁 2 meses grátis</span>
<label class="btn btn-outline-primary" for="monthly">Mensal</label> <div class="pricing-pill">
<button type="button" class="pill-btn pill-active" id="btnMonthly">Mensal</button>
<input type="radio" class="btn-check" name="billingPeriod" id="yearly" autocomplete="off"> <button type="button" class="pill-btn" id="btnYearly">Anual</button>
<label class="btn btn-outline-primary" for="yearly"> </div>
Anual
<span class="badge bg-success ms-1">2 meses grátis</span>
</label>
</div> </div>
</div> </div>
<style>
.pricing-toggle-wrapper {
position: relative;
display: inline-block;
}
.pricing-savings-chip {
position: absolute;
top: -24px;
right: -4px;
background: #198754;
color: #fff;
font-size: 0.68rem;
font-weight: 700;
padding: 2px 9px;
border-radius: 20px;
white-space: nowrap;
letter-spacing: 0.02em;
}
.pricing-pill {
display: inline-flex;
background: #ededf3;
border-radius: 50px;
padding: 4px;
gap: 2px;
}
.pill-btn {
padding: 8px 32px;
border-radius: 50px;
border: none;
background: transparent;
color: #555;
font-weight: 600;
font-size: 0.95rem;
cursor: pointer;
transition: background 0.18s, color 0.18s, box-shadow 0.18s;
white-space: nowrap;
}
.pill-btn.pill-active {
background: var(--tenant-primary, #667eea);
color: #fff;
box-shadow: 0 2px 10px rgba(0,0,0,.18);
}
.pricing-cards .card-body {
font-size: 0.875rem;
}
.pricing-cards .card-header h5 {
font-size: 0.95rem;
}
</style>
</div> </div>
<div class="row g-3 justify-content-center pricing-cards"> <div class="row g-4 row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-5 pricing-cards">
<!-- Plano Trial --> <!-- Plano Trial -->
<div class="col-xl-2 col-lg-4 col-md-6"> <div class="col">
<div class="card h-100 border-0 shadow-sm"> <div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-success bg-opacity-10 text-center py-4"> <div class="card-header bg-success bg-opacity-10 text-center py-4">
<h5 class="mb-0">Trial Gratuito</h5> <h5 class="mb-0">Trial Gratuito</h5>
@ -76,7 +122,7 @@
</div> </div>
<!-- Plano Básico --> <!-- Plano Básico -->
<div class="col-xl-2 col-lg-4 col-md-6"> <div class="col">
<div class="card h-100 border-0 shadow-sm"> <div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-light text-center py-4"> <div class="card-header bg-light text-center py-4">
<h5 class="mb-0">Básico</h5> <h5 class="mb-0">Básico</h5>
@ -150,7 +196,7 @@
</div> </div>
<!-- Plano Profissional (Decoy) --> <!-- Plano Profissional (Decoy) -->
<div class="col-xl-2 col-lg-4 col-md-6"> <div class="col">
<div class="card h-100 border-0 shadow-sm"> <div class="card h-100 border-0 shadow-sm">
<div class="card-header bg-warning bg-opacity-10 text-center py-4"> <div class="card-header bg-warning bg-opacity-10 text-center py-4">
<h5 class="mb-0">Profissional</h5> <h5 class="mb-0">Profissional</h5>
@ -224,12 +270,10 @@
</div> </div>
<!-- Plano Premium (Mais Popular) --> <!-- Plano Premium (Mais Popular) -->
<div class="col-xl-2 col-lg-4 col-md-6"> <div class="col">
<div class="card h-100 border-primary shadow position-relative"> <div class="card h-100 border-primary shadow">
<div class="position-absolute top-0 start-50 translate-middle"> <div class="card-header bg-primary text-white text-center pt-3 pb-4">
<span class="badge bg-primary px-3 py-2">Mais Popular</span> <span class="badge bg-white text-primary px-3 py-1 mb-2 d-inline-block" style="font-size:0.7rem;letter-spacing:.04em;">⭐ Mais Popular</span>
</div>
<div class="card-header bg-primary text-white text-center py-4">
<h5 class="mb-0">Premium</h5> <h5 class="mb-0">Premium</h5>
<div class="mt-3"> <div class="mt-3">
<div class="pricing-monthly"> <div class="pricing-monthly">
@ -309,12 +353,10 @@
</div> </div>
<!-- Plano Premium + Afiliados --> <!-- Plano Premium + Afiliados -->
<div class="col-xl-2 col-lg-4 col-md-6"> <div class="col">
<div class="card h-100 border-success shadow"> <div class="card h-100 border-success shadow">
<div class="position-absolute top-0 start-50 translate-middle pricing-premium-badge"> <div class="card-header bg-success text-white text-center pt-3 pb-4">
<span class="badge bg-success px-3 py-2">Novo!</span> <span class="badge bg-white text-success px-3 py-1 mb-2 d-inline-block" style="font-size:0.7rem;letter-spacing:.04em;">🆕 Novo!</span>
</div>
<div class="card-header bg-success text-white text-center py-4">
<h5 class="mb-0">Premium + Afiliados</h5> <h5 class="mb-0">Premium + Afiliados</h5>
<div class="mt-3"> <div class="mt-3">
<div class="pricing-monthly"> <div class="pricing-monthly">
@ -394,6 +436,7 @@
</div> </div>
</div> </div>
<div class="container">
<!-- Comparação de recursos --> <!-- Comparação de recursos -->
<div class="mt-5 pt-5"> <div class="mt-5 pt-5">
<h2 class="text-center mb-4">Compare todos os recursos</h2> <h2 class="text-center mb-4">Compare todos os recursos</h2>
@ -535,6 +578,7 @@
</div> </div>
</div> </div>
</div> </div>
</div><!-- /container inner -->
</div> </div>
@if (TempData["Success"] != null) @if (TempData["Success"] != null)
@ -587,22 +631,20 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const monthlyRadio = document.getElementById('monthly'); const btnMonthly = document.getElementById('btnMonthly');
const yearlyRadio = document.getElementById('yearly'); const btnYearly = document.getElementById('btnYearly');
const monthlyElements = document.querySelectorAll('.pricing-monthly'); const monthlyElements = document.querySelectorAll('.pricing-monthly');
const yearlyElements = document.querySelectorAll('.pricing-yearly'); const yearlyElements = document.querySelectorAll('.pricing-yearly');
function togglePricing() { function setPricing(period) {
if (yearlyRadio.checked) { const isYearly = period === 'yearly';
monthlyElements.forEach(el => el.classList.add('d-none')); btnMonthly.classList.toggle('pill-active', !isYearly);
yearlyElements.forEach(el => el.classList.remove('d-none')); btnYearly.classList.toggle('pill-active', isYearly);
} else { monthlyElements.forEach(el => el.classList.toggle('d-none', isYearly));
monthlyElements.forEach(el => el.classList.remove('d-none')); yearlyElements.forEach(el => el.classList.toggle('d-none', !isYearly));
yearlyElements.forEach(el => el.classList.add('d-none'));
}
} }
monthlyRadio.addEventListener('change', togglePricing); btnMonthly.addEventListener('click', () => setPricing('monthly'));
yearlyRadio.addEventListener('change', togglePricing); btnYearly.addEventListener('click', () => setPricing('yearly'));
}); });
</script> </script>

View File

@ -51,8 +51,6 @@
--tenant-gradient: @tenant.HeroGradient; --tenant-gradient: @tenant.HeroGradient;
} }
.hero-section { background: var(--tenant-gradient) !important; } .hero-section { background: var(--tenant-gradient) !important; }
.bg-home-blue { background: var(--tenant-gradient) !important; }
.bg-home-blue .navbar-collapse { background-color: color-mix(in srgb, var(--tenant-primary) 80%, black) !important; }
.btn-primary { background-color: var(--tenant-primary) !important; border-color: var(--tenant-primary) !important; } .btn-primary { background-color: var(--tenant-primary) !important; border-color: var(--tenant-primary) !important; }
.btn-primary:hover { background-color: var(--tenant-primary-dark) !important; border-color: var(--tenant-primary-dark) !important; } .btn-primary:hover { background-color: var(--tenant-primary-dark) !important; border-color: var(--tenant-primary-dark) !important; }
.btn-outline-primary { color: var(--tenant-primary) !important; border-color: var(--tenant-primary) !important; } .btn-outline-primary { color: var(--tenant-primary) !important; border-color: var(--tenant-primary) !important; }
@ -101,14 +99,14 @@
100% { background-position: 200% 0; } 100% { background-position: 200% 0; }
} }
/* Destacar item ativo do menu */ /* Destacar item ativo do menu — usa cor do tenant */
.nav-link.active { .nav-link.active {
background-color: rgba(0, 123, 255, 0.1) !important; background-color: color-mix(in srgb, var(--tenant-primary, #0d6efd) 10%, transparent) !important;
border-radius: 6px !important; border-radius: 6px !important;
font-weight: 600 !important; font-weight: 600 !important;
} }
/* Para homepage (fundo azul) */ /* Para homepage (fundo colorido) */
.bg-home-blue .nav-link.active { .bg-home-blue .nav-link.active {
background-color: rgba(255, 255, 255, 0.2) !important; background-color: rgba(255, 255, 255, 0.2) !important;
} }
@ -159,7 +157,8 @@
<div id="loading-bar"></div> <div id="loading-bar"></div>
<header> <header>
<nav class="navbar navbar-expand-lg navbar-toggleable-sm navbar-light fixed-top @(ViewBag.IsHomePage == true ? "bg-home-blue" : "bg-dashboard")" id="mainNavbar"> <nav class="navbar navbar-expand-lg navbar-toggleable-sm navbar-light fixed-top @(ViewBag.IsHomePage == true ? "bg-home-blue" : "bg-dashboard")" id="mainNavbar"
style="@(ViewBag.IsHomePage == true ? $"background-color: {tenant.PrimaryColor} !important;" : "")">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand fw-bold @(ViewBag.IsHomePage == true ? "text-white" : "text-primary")" <a class="navbar-brand fw-bold @(ViewBag.IsHomePage == true ? "text-white" : "text-primary")"
asp-area="" asp-controller="Home" asp-action="Index"> asp-area="" asp-controller="Home" asp-action="Index">

View File

@ -1,25 +1,25 @@
{ {
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Debug", "Default": "Debug",
"System": "Information", "System": "Information",
"Microsoft": "Information", "Microsoft": "Information",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"Stripe": { "Stripe": {
"PublishableKey": "pk_test_51RjUmIBMIadsOxJVP4bWc54pHEOSf5km1hpOkOBSoGVoKxI46N4KSWtevpXCSq68OjFazBuXmPJGBwZ1KDN5MNJy003lj1YmAS", "PublishableKey": "pk_test_51RjUmIBMIadsOxJVP4bWc54pHEOSf5km1hpOkOBSoGVoKxI46N4KSWtevpXCSq68OjFazBuXmPJGBwZ1KDN5MNJy003lj1YmAS",
"SecretKey": "sk_test_51RjUmIBMIadsOxJVeqsMFxnZ8ePR7d8IbnaF4sAwBVJv9rrfODPEQ2C9fF3beoABpITdfzEk0ZDzGTTQfvKv63xI00PeZoABGO", "SecretKey": "sk_test_51RjUmIBMIadsOxJVeqsMFxnZ8ePR7d8IbnaF4sAwBVJv9rrfODPEQ2C9fF3beoABpITdfzEk0ZDzGTTQfvKv63xI00PeZoABGO",
"WebhookSecret": "whsec_8d189c137ff170ab5e62498003512b9d073e2db50c50ed7d8712b7ef11a37543", "WebhookSecret": "whsec_8d189c137ff170ab5e62498003512b9d073e2db50c50ed7d8712b7ef11a37543",
"Environment": "test" "Environment": "test"
}, },
"Serilog": { "Serilog": {
"OpenSearchUrl": "http://192.168.0.100:9200", "OpenSearchUrl": "http://192.168.0.100:9200"
}, },
"DetailedErrors": true, "DetailedErrors": true,
"MongoDb": { "MongoDb": {
"ConnectionString": "mongodb://localhost:27017", "ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "BCardsDB_Dev" "DatabaseName": "BCardsDB_Dev"
}, },
"BaseUrl": "https://localhost:49178" "BaseUrl": "https://localhost:49178"
} }

View File

@ -6,7 +6,11 @@
"WebhookSecret": "whsec_8d189c137ff170ab5e62498003512b9d073e2db50c50ed7d8712b7ef11a37543", "WebhookSecret": "whsec_8d189c137ff170ab5e62498003512b9d073e2db50c50ed7d8712b7ef11a37543",
"Environment": "test" "Environment": "test"
}, },
"Serilog": {
"OpenSearchUrl": "http://192.168.0.100:9200"
},
"MongoDb": { "MongoDb": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "LusLinksDB" "DatabaseName": "LusLinksDB"
}, },
"Tenant": { "Tenant": {
@ -32,6 +36,9 @@
"CtaButtonText": "Criar Minha Bio de Fé", "CtaButtonText": "Criar Minha Bio de Fé",
"MetaKeywords": "bio links pastor, página ministério, linktree cristão, links religiosos, página iglesia, bio pastor, links igreja", "MetaKeywords": "bio links pastor, página ministério, linktree cristão, links religiosos, página iglesia, bio pastor, links igreja",
"FooterTagline": "Conectando fé e comunidade.", "FooterTagline": "Conectando fé e comunidade.",
"HeroGradient": "linear-gradient(135deg, #5b9bd5 0%, #1a5276 100%)",
"PrimaryColor": "#2471a3",
"PrimaryColorDark": "#1a5276",
"AllowedLinkTypes": [ "AllowedLinkTypes": [
{ "Icon": "fas fa-globe", "Label": "🌐 Site / Ministério", "Prefix": "https://", "Placeholder": "ministerio.com.br", "Instructions": "Digite o domínio do site", "Color": "bg-primary" }, { "Icon": "fas fa-globe", "Label": "🌐 Site / Ministério", "Prefix": "https://", "Placeholder": "ministerio.com.br", "Instructions": "Digite o domínio do site", "Color": "bg-primary" },
{ "Icon": "fas fa-envelope", "Label": "✉️ Email", "Prefix": "mailto:", "Placeholder": "contato@ministerio.com", "Instructions": "Digite apenas o email", "Color": "bg-success" }, { "Icon": "fas fa-envelope", "Label": "✉️ Email", "Prefix": "mailto:", "Placeholder": "contato@ministerio.com", "Instructions": "Digite apenas o email", "Color": "bg-success" },

View File

@ -7,8 +7,13 @@
"Environment": "test" "Environment": "test"
}, },
"MongoDb": { "MongoDb": {
"ConnectionString": "mongodb://localhost:27017",
"DatabaseName": "SpicyLinksDB" "DatabaseName": "SpicyLinksDB"
}, },
"Serilog": {
"OpenSearchUrl": "http://192.168.0.100:9200"
},
"Tenant": { "Tenant": {
"SiteName": "SpicyLinks", "SiteName": "SpicyLinks",
"SiteDescription": "A plataforma discreta e segura para criadores de conteúdo adulto. Reúna suas assinaturas, lista de desejos, redes sociais e conteúdo exclusivo em uma única bio.", "SiteDescription": "A plataforma discreta e segura para criadores de conteúdo adulto. Reúna suas assinaturas, lista de desejos, redes sociais e conteúdo exclusivo em uma única bio.",
@ -23,9 +28,21 @@
"HeroCtaText": "Criar Minha Bio", "HeroCtaText": "Criar Minha Bio",
"FeaturesHeadline": "Por que criadores escolhem o {SiteName}?", "FeaturesHeadline": "Por que criadores escolhem o {SiteName}?",
"Features": [ "Features": [
{ "Icon": "❤️", "Title": "Tudo num Só Link", "Description": "Instagram, Twitter/X, OnlyFans, lista de desejos e mais — tudo em uma bio única, elegante e fácil de compartilhar." }, {
{ "Icon": "🔒", "Title": "Verificação de Idade", "Description": "Acesso protegido com verificação de idade automática. Plataforma segura, discreta e responsável." }, "Icon": "❤️",
{ "Icon": "📊", "Title": "Saiba Quem Te Visita", "Description": "Analytics detalhado de cliques, visualizações e origem do tráfego para otimizar suas conversões." } "Title": "Tudo num Só Link",
"Description": "Instagram, Twitter/X, OnlyFans, lista de desejos e mais — tudo em uma bio única, elegante e fácil de compartilhar."
},
{
"Icon": "🔒",
"Title": "Verificação de Idade",
"Description": "Acesso protegido com verificação de idade automática. Plataforma segura, discreta e responsável."
},
{
"Icon": "📊",
"Title": "Saiba Quem Te Visita",
"Description": "Analytics detalhado de cliques, visualizações e origem do tráfego para otimizar suas conversões."
}
], ],
"CtaHeadline": "Pronta para monetizar seu conteúdo?", "CtaHeadline": "Pronta para monetizar seu conteúdo?",
"CtaDescription": "Milhares de criadoras já centralizam seus links e aumentam suas conversões com o SpicyLinks.", "CtaDescription": "Milhares de criadoras já centralizam seus links e aumentam suas conversões com o SpicyLinks.",
@ -36,24 +53,128 @@
"PrimaryColor": "#e63946", "PrimaryColor": "#e63946",
"PrimaryColorDark": "#c1121f", "PrimaryColorDark": "#c1121f",
"DefaultCategories": [ "DefaultCategories": [
{ "Icon": "📸", "Name": "Modelos", "Slug": "modelos", "Description": "Modelos e criadores de conteúdo visual", "SeoKeywords": ["modelo", "fotografia", "conteúdo", "criadora"] }, {
{ "Icon": "⭐", "Name": "Influencers", "Slug": "influencers", "Description": "Influencers e personalidades digitais", "SeoKeywords": ["influencer", "digital", "social media"] }, "Icon": "📸",
{ "Icon": "💪", "Name": "Fitness", "Slug": "fitness", "Description": "Criadores de conteúdo fitness e lifestyle", "SeoKeywords": ["fitness", "academia", "saúde", "corpo"] }, "Name": "Modelos",
{ "Icon": "🎨", "Name": "Arte", "Slug": "arte", "Description": "Artistas e criadores de conteúdo visual", "SeoKeywords": ["arte", "ilustração", "design", "criativo"] }, "Slug": "modelos",
{ "Icon": "🎵", "Name": "Música", "Slug": "musica", "Description": "Músicos e cantores independentes", "SeoKeywords": ["música", "cantor", "artista", "show"] }, "Description": "Modelos e criadores de conteúdo visual",
{ "Icon": "🎮", "Name": "Gaming", "Slug": "gaming", "Description": "Streamers e criadores de conteúdo gamer", "SeoKeywords": ["gaming", "streamer", "games", "twitch"] }, "SeoKeywords": [ "modelo", "fotografia", "conteúdo", "criadora" ]
{ "Icon": "🦸", "Name": "Cosplay", "Slug": "cosplay", "Description": "Cosplayers e criadores de fantasia", "SeoKeywords": ["cosplay", "anime", "fantasia", "cosplayer"] }, },
{ "Icon": "💋", "Name": "Lifestyle", "Slug": "lifestyle", "Description": "Criadores de conteúdo lifestyle e entretenimento", "SeoKeywords": ["lifestyle", "entretenimento", "diversão"] } {
"Icon": "⭐",
"Name": "Influencers",
"Slug": "influencers",
"Description": "Influencers e personalidades digitais",
"SeoKeywords": [ "influencer", "digital", "social media" ]
},
{
"Icon": "💪",
"Name": "Fitness",
"Slug": "fitness",
"Description": "Criadores de conteúdo fitness e lifestyle",
"SeoKeywords": [ "fitness", "academia", "saúde", "corpo" ]
},
{
"Icon": "🎨",
"Name": "Arte",
"Slug": "arte",
"Description": "Artistas e criadores de conteúdo visual",
"SeoKeywords": [ "arte", "ilustração", "design", "criativo" ]
},
{
"Icon": "🎵",
"Name": "Música",
"Slug": "musica",
"Description": "Músicos e cantores independentes",
"SeoKeywords": [ "música", "cantor", "artista", "show" ]
},
{
"Icon": "🎮",
"Name": "Gaming",
"Slug": "gaming",
"Description": "Streamers e criadores de conteúdo gamer",
"SeoKeywords": [ "gaming", "streamer", "games", "twitch" ]
},
{
"Icon": "🦸",
"Name": "Cosplay",
"Slug": "cosplay",
"Description": "Cosplayers e criadores de fantasia",
"SeoKeywords": [ "cosplay", "anime", "fantasia", "cosplayer" ]
},
{
"Icon": "💋",
"Name": "Lifestyle",
"Slug": "lifestyle",
"Description": "Criadores de conteúdo lifestyle e entretenimento",
"SeoKeywords": [ "lifestyle", "entretenimento", "diversão" ]
}
], ],
"AllowedLinkTypes": [ "AllowedLinkTypes": [
{ "Icon": "fas fa-globe", "Label": "🌐 Site Geral", "Prefix": "https://", "Placeholder": "exemplo.com", "Instructions": "Digite o domínio e caminho", "Color": "bg-primary" }, {
{ "Icon": "fas fa-envelope", "Label": "✉️ Email", "Prefix": "mailto:", "Placeholder": "seuemail@exemplo.com", "Instructions": "Digite apenas o email", "Color": "bg-success" }, "Icon": "fas fa-globe",
{ "Icon": "fas fa-phone", "Label": "📞 Telefone", "Prefix": "tel:", "Placeholder": "5511999999999", "Instructions": "Número com código do país", "Color": "bg-success" }, "Label": "🌐 Site Geral",
{ "Icon": "fab fa-instagram", "Label": "📸 Instagram", "Prefix": "https://instagram.com/","Placeholder": "seu.usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-danger" }, "Prefix": "https://",
{ "Icon": "fab fa-twitter", "Label": "🐦 Twitter/X", "Prefix": "https://x.com/", "Placeholder": "seu_usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-dark" }, "Placeholder": "exemplo.com",
{ "Icon": "fab fa-tiktok", "Label": "🎵 TikTok", "Prefix": "https://tiktok.com/@", "Placeholder": "seu.usuario", "Instructions": "Digite apenas seu usuário", "Color": "bg-dark" }, "Instructions": "Digite o domínio e caminho",
{ "Icon": "fas fa-shopping-cart","Label": "🛒 Lista de Desejos","Prefix": "https://", "Placeholder": "wishlist.com/...", "Instructions": "Link para lista de desejos", "Color": "bg-warning" }, "Color": "bg-primary"
{ "Icon": "fas fa-heart", "Label": "❤️ Assinatura", "Prefix": "https://", "Placeholder": "plataforma.com/...", "Instructions": "Link para plataforma paga", "Color": "bg-danger" } },
{
"Icon": "fas fa-envelope",
"Label": "✉️ Email",
"Prefix": "mailto:",
"Placeholder": "seuemail@exemplo.com",
"Instructions": "Digite apenas o email",
"Color": "bg-success"
},
{
"Icon": "fas fa-phone",
"Label": "📞 Telefone",
"Prefix": "tel:",
"Placeholder": "5511999999999",
"Instructions": "Número com código do país",
"Color": "bg-success"
},
{
"Icon": "fab fa-instagram",
"Label": "📸 Instagram",
"Prefix": "https://instagram.com/",
"Placeholder": "seu.usuario",
"Instructions": "Digite apenas seu usuário",
"Color": "bg-danger"
},
{
"Icon": "fab fa-twitter",
"Label": "🐦 Twitter/X",
"Prefix": "https://x.com/",
"Placeholder": "seu_usuario",
"Instructions": "Digite apenas seu usuário",
"Color": "bg-dark"
},
{
"Icon": "fab fa-tiktok",
"Label": "🎵 TikTok",
"Prefix": "https://tiktok.com/@",
"Placeholder": "seu.usuario",
"Instructions": "Digite apenas seu usuário",
"Color": "bg-dark"
},
{
"Icon": "fas fa-shopping-cart",
"Label": "🛒 Lista de Desejos",
"Prefix": "https://",
"Placeholder": "wishlist.com/...",
"Instructions": "Link para lista de desejos",
"Color": "bg-warning"
},
{
"Icon": "fas fa-heart",
"Label": "❤️ Assinatura",
"Prefix": "https://",
"Placeholder": "plataforma.com/...",
"Instructions": "Link para plataforma paga",
"Color": "bg-danger"
}
] ]
}, },
"SendGrid": { "SendGrid": {

View File

@ -193,6 +193,9 @@
"CtaButtonText": "Criar Minha Página Grátis", "CtaButtonText": "Criar Minha Página Grátis",
"MetaKeywords": "cartão digital, página de links, bio links, linktree brasil, página profissional, corretor, advogado, médico, consultor", "MetaKeywords": "cartão digital, página de links, bio links, linktree brasil, página profissional, corretor, advogado, médico, consultor",
"FooterTagline": "Sua presença digital profissional, simplificada.", "FooterTagline": "Sua presença digital profissional, simplificada.",
"HeroGradient": "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
"PrimaryColor": "#667eea",
"PrimaryColorDark": "#5a6fd6",
"AllowedLinkTypes": [ "AllowedLinkTypes": [
{ "Icon": "fas fa-globe", "Label": "🌐 Site Geral", "Prefix": "https://", "Placeholder": "exemplo.com", "Instructions": "Digite apenas o domínio e caminho (sem https://)", "Color": "bg-primary" }, { "Icon": "fas fa-globe", "Label": "🌐 Site Geral", "Prefix": "https://", "Placeholder": "exemplo.com", "Instructions": "Digite apenas o domínio e caminho (sem https://)", "Color": "bg-primary" },
{ "Icon": "fas fa-shopping-cart", "Label": "🛒 Loja/E-commerce", "Prefix": "https://", "Placeholder": "minhaloja.com/produto", "Instructions": "Digite apenas o domínio e caminho da sua loja", "Color": "bg-success" }, { "Icon": "fas fa-shopping-cart", "Label": "🛒 Loja/E-commerce", "Prefix": "https://", "Placeholder": "minhaloja.com/produto", "Instructions": "Digite apenas o domínio e caminho da sua loja", "Color": "bg-success" },

View File

@ -143,9 +143,9 @@ body > .container-fluid {
transition: all 0.3s ease; transition: all 0.3s ease;
} }
/* Menu Home (gradiente) */ /* Menu Home — cor sólida primária do tenant (gradiente em elemento estreito parece diferente do hero) */
.bg-home-blue { .bg-home-blue {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; background-color: var(--tenant-primary, #667eea) !important;
} }
.bg-home-blue .navbar-brand, .bg-home-blue .navbar-brand,
@ -185,6 +185,6 @@ body > .container-fluid {
box-shadow: 0 4px 15px rgba(0,0,0,0.1); box-shadow: 0 4px 15px rgba(0,0,0,0.1);
} }
.bg-home-blue .navbar-collapse { .bg-home-blue .navbar-collapse {
background-color: rgba(118, 75, 162, 0.95); /* Cor do gradiente para o menu recolhido */ background-color: color-mix(in srgb, var(--tenant-primary, #764ba2) 85%, black);
} }
} }