Add public showcase landing, doc summary cards, and chunk toggle

- index.php: public showcase landing page (hero, how-it-works, capabilities,
  evidence mock, login form) visible to unauthenticated visitors; full OG/SEO
  meta; app shell hidden behind auth as before
- tools.css: showcase section styles (gradient hero, step cards, capability
  grid, CTA button, evidence mock, footer)
- LegalTools.php: sourceFromChunk() batch-fetches doc_summaries from RAG DB
  for non-private chunks; excerpt shows doc summary when available, falls back
  to raw chunk text; chunk_text field always carries the raw excerpt
- tools.js: renderEvidenceItem() shows doc summary as card body; adds a
  collapsible "View chunk" toggle when summary differs from raw chunk text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-12 08:37:36 +02:00
parent 3442b1b6d3
commit 1f4f01bda3
4 changed files with 554 additions and 34 deletions
+362
View File
@@ -511,3 +511,365 @@ p {
grid-template-columns: 1fr;
}
}
/* ─── Public showcase landing ──────────────────────────────────────────────── */
.showcase-page {
display: flex;
flex-direction: column;
min-height: 100vh;
background: var(--bg);
}
.showcase-header {
background:
linear-gradient(135deg, rgba(15, 118, 110, 0.92), rgba(17, 94, 89, 0.97) 70%),
linear-gradient(315deg, rgba(194, 65, 12, 0.18), transparent 40%);
color: #fff;
padding: 64px 24px 72px;
}
.showcase-header-inner {
max-width: 860px;
margin: 0 auto;
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 32px;
flex-wrap: wrap;
}
.showcase-brand .eyebrow {
color: rgba(255,255,255,0.72);
}
.showcase-title {
margin: 0 0 10px;
font-size: clamp(2rem, 5vw, 3rem);
font-weight: 800;
line-height: 1.1;
color: #fff;
}
.showcase-tagline {
margin: 0 0 18px;
font-size: 1.1rem;
color: rgba(255,255,255,0.82);
}
.powered-badge {
display: inline-block;
background: rgba(255,255,255,0.14);
border: 1px solid rgba(255,255,255,0.28);
border-radius: 999px;
padding: 4px 14px;
font-size: 0.78rem;
font-weight: 600;
color: rgba(255,255,255,0.9);
text-decoration: none;
transition: background 0.15s;
}
.powered-badge:hover {
background: rgba(255,255,255,0.22);
}
.cta-button {
display: inline-block;
background: var(--coral);
color: #fff;
text-decoration: none;
padding: 14px 28px;
border-radius: 8px;
font-weight: 700;
font-size: 1rem;
white-space: nowrap;
transition: background 0.15s, transform 0.1s;
flex-shrink: 0;
}
.cta-button:hover {
background: #b83a0b;
transform: translateY(-1px);
}
.section-inner {
max-width: 860px;
margin: 0 auto;
padding: 0 24px;
}
.section-heading {
margin: 0 0 8px;
font-size: 1.5rem;
font-weight: 800;
color: var(--ink);
}
.section-sub {
margin: 0 0 36px;
color: var(--muted);
font-size: 0.98rem;
}
/* How it works */
.hiw-section {
padding: 64px 0;
background: var(--panel);
border-bottom: 1px solid var(--line);
}
.hiw-steps {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 28px;
}
.hiw-step {
background: var(--bg);
border: 1px solid var(--line);
border-radius: 8px;
padding: 24px;
}
.hiw-num {
font-size: 2rem;
font-weight: 800;
color: var(--teal);
opacity: 0.4;
margin-bottom: 12px;
line-height: 1;
}
.hiw-step h3 {
margin: 0 0 8px;
font-size: 1rem;
font-weight: 700;
}
.hiw-step p {
margin: 0;
color: var(--muted);
font-size: 0.9rem;
line-height: 1.6;
}
/* Capabilities */
.cap-section {
padding: 64px 0;
border-bottom: 1px solid var(--line);
}
.cap-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(155px, 1fr));
gap: 16px;
}
.cap-card {
background: var(--panel);
border: 1px solid var(--line);
border-radius: 8px;
padding: 20px;
transition: box-shadow 0.15s;
}
.cap-card:hover {
box-shadow: 0 4px 16px rgba(15,118,110,0.1);
}
.cap-label {
display: inline-block;
background: var(--soft-teal);
color: var(--teal-dark);
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
border-radius: 999px;
padding: 3px 10px;
margin-bottom: 10px;
}
.cap-card h3 {
margin: 0 0 6px;
font-size: 0.95rem;
font-weight: 700;
}
.cap-card p {
margin: 0;
color: var(--muted);
font-size: 0.85rem;
line-height: 1.55;
}
/* Evidence trail explainer */
.evidence-section {
padding: 64px 0;
background: var(--panel);
border-bottom: 1px solid var(--line);
}
.evidence-inner {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
align-items: start;
}
.evidence-copy h2 {
margin: 0 0 14px;
font-size: 1.5rem;
font-weight: 800;
}
.evidence-copy p {
color: var(--muted);
line-height: 1.7;
margin: 0 0 18px;
}
.evidence-list {
margin: 0;
padding-left: 20px;
color: var(--muted);
font-size: 0.9rem;
line-height: 1.8;
}
.evidence-mock {
background: var(--bg);
border: 1px solid var(--line);
border-radius: 8px;
padding: 20px;
}
.mock-label {
margin: 0 0 16px;
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--teal);
}
.mock-step {
display: flex;
gap: 12px;
margin-bottom: 14px;
padding-bottom: 14px;
border-bottom: 1px solid var(--line);
font-size: 0.85rem;
}
.mock-step:last-child {
border-bottom: 0;
margin-bottom: 0;
padding-bottom: 0;
}
.mock-step p {
margin: 3px 0 0;
color: var(--muted);
font-size: 0.82rem;
}
.mock-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--teal);
flex-shrink: 0;
margin-top: 4px;
}
.mock-dot--amber {
background: var(--amber);
}
.mock-done .mock-dot {
background: var(--teal);
}
/* Access / login gate */
.access-section {
padding: 64px 24px;
display: flex;
justify-content: center;
background: var(--bg);
}
/* Showcase footer */
.showcase-footer {
padding: 32px 24px;
text-align: center;
border-top: 1px solid var(--line);
color: var(--muted);
font-size: 0.85rem;
}
.showcase-footer a {
color: var(--teal);
text-decoration: none;
}
.showcase-footer a:hover {
text-decoration: underline;
}
.footer-disclaimer {
margin: 6px 0 0;
font-size: 0.78rem;
opacity: 0.7;
}
@media (max-width: 640px) {
.showcase-header-inner {
flex-direction: column;
align-items: flex-start;
}
.evidence-inner {
grid-template-columns: 1fr;
}
.cap-grid {
grid-template-columns: 1fr 1fr;
}
}
/* Source card chunk toggle */
.chunk-details {
margin-top: 0.625rem;
}
.chunk-toggle {
cursor: pointer;
font-size: 0.75rem;
font-weight: 600;
color: var(--teal);
user-select: none;
list-style: none;
}
.chunk-toggle::-webkit-details-marker {
display: none;
}
.chunk-details[open] .chunk-toggle {
color: var(--teal-dark);
}
.chunk-text {
margin-top: 0.5rem;
font-size: 0.7rem;
line-height: 1.55;
background: #f0fdf9;
border: 1px solid #ccfbf1;
border-radius: 6px;
padding: 0.625rem 0.75rem;
white-space: pre-wrap;
overflow-x: auto;
color: #374151;
}
+10 -1
View File
@@ -60,7 +60,7 @@ const els = {};
document.addEventListener('DOMContentLoaded', () => {
Object.assign(els, {
gate: document.querySelector('#passcodeGate'),
gate: document.querySelector('#publicLanding'),
app: document.querySelector('#appShell'),
passcodeForm: document.querySelector('#passcodeForm'),
loginEmail: document.querySelector('#loginEmail'),
@@ -291,17 +291,26 @@ function renderEvidence(data) {
function renderEvidenceItem(item) {
const title = item.title || item.citation || 'Source';
const body = item.excerpt || item.why_it_matters || item.citation || '';
const chunkText = item.chunk_text || '';
const meta = [
item.package_or_corpus,
item.section,
item.score !== undefined && item.score !== null ? `score ${item.score}` : '',
].filter(Boolean).join(' · ');
const chunkToggle = (chunkText && chunkText !== body) ? `
<details class="chunk-details">
<summary class="chunk-toggle">View chunk</summary>
<pre class="chunk-text">${escapeHtml(chunkText)}</pre>
</details>
` : '';
return `
<article class="source-card">
<h4>${escapeHtml(title)}</h4>
${meta ? `<p class="source-meta">${escapeHtml(meta)}</p>` : ''}
<p>${escapeHtml(body)}</p>
${chunkToggle}
</article>
`;
}