feat: free-tier credit system + Syttende Mai access for Google users

- FreeTier.php: credit check/deduct/reset engine with hourly rate limit
- bootstrap.php: dbnmDb() singleton, dbnToolsIsFreeTier(), credit gate helpers
- index.php: store tier=free|approved in session from SSO JWT
- All 7 API endpoints: credit gate (402/429) + X-Credits-Remaining header
- layout.php: credit meta tag, JS balance var, Syttende Mai banner (05-17 only)
- tools.js: credit badge in topbar, 402 modal, 429 toast, dbnUpdateCredits()
- barnevernet.js + deep-research.js: wire 402/429 handling for NDJSON streams
- tools.css: styles for credit badge, no-credits modal, rate-limit toast

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-16 21:05:08 +02:00
parent 568314c554
commit 8b77acb828
15 changed files with 464 additions and 2 deletions
+111
View File
@@ -4313,3 +4313,114 @@ body {
.site-footer__inner { grid-template-columns: 1fr; }
.site-footer__bottom { flex-direction: column; }
}
/* ── Syttende Mai banner ──────────────────────────────────────────────────── */
.syttende-mai-banner {
background: #ba0c2f;
color: #fff;
padding: 9px 20px;
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
font-size: 0.875rem;
font-weight: 500;
letter-spacing: 0.01em;
position: sticky;
top: 0;
z-index: 200;
}
.syttende-mai-close {
background: none;
border: none;
color: rgba(255,255,255,0.75);
cursor: pointer;
font-size: 1rem;
line-height: 1;
padding: 0 4px;
margin-left: 8px;
}
.syttende-mai-close:hover { color: #fff; }
/* ── Free-tier credit badge ──────────────────────────────────────────────── */
.credit-badge {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 10px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 600;
background: var(--soft-teal);
color: var(--teal-dark);
border: 1px solid rgba(15, 118, 110, 0.25);
white-space: nowrap;
cursor: default;
}
.credit-badge.is-low {
background: #fff7ed;
color: #92400e;
border-color: rgba(183, 121, 31, 0.3);
}
.credit-badge.is-empty {
background: #fff0e8;
color: #c2410c;
border-color: rgba(194, 65, 12, 0.3);
}
/* ── Credit-empty modal ───────────────────────────────────────────────────── */
.credit-modal-overlay {
position: fixed;
inset: 0;
background: rgba(22, 19, 15, 0.55);
display: flex;
align-items: center;
justify-content: center;
z-index: 500;
}
.credit-modal {
background: var(--panel);
border-radius: 12px;
padding: 32px 28px 24px;
max-width: 380px;
width: calc(100% - 40px);
box-shadow: var(--shadow);
text-align: center;
}
.credit-modal__icon { font-size: 2.5rem; margin-bottom: 12px; }
.credit-modal h3 { margin: 0 0 8px; font-size: 1.15rem; color: var(--ink); }
.credit-modal p { color: var(--muted); font-size: 0.9rem; margin: 0 0 20px; line-height: 1.55; }
.credit-modal__actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.credit-modal__actions a {
padding: 8px 20px;
border-radius: 8px;
font-size: 0.875rem;
font-weight: 600;
text-decoration: none;
display: inline-block;
}
.credit-modal__cta { background: var(--teal); color: #fff; }
.credit-modal__cta:hover { background: var(--teal-dark); }
.credit-modal__dismiss { background: var(--line); color: var(--ink); }
.credit-modal__dismiss:hover { background: #c8cdd8; }
/* ── Rate-limit toast ────────────────────────────────────────────────────── */
.credit-toast {
position: fixed;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
background: #1b2330;
color: #fff;
padding: 10px 20px;
border-radius: 8px;
font-size: 0.875rem;
z-index: 500;
white-space: nowrap;
box-shadow: 0 4px 16px rgba(0,0,0,0.25);
animation: toastIn 0.2s ease;
}
@keyframes toastIn {
from { opacity: 0; transform: translateX(-50%) translateY(8px); }
to { opacity: 1; transform: translateX(-50%) translateY(0); }
}