7c4b161f04
- korrespond-about.php: marketing/hype page with hero image, features, 3-step pipeline, screenshot gallery, dbn-legal-agent LLM spotlight, authority coverage grid, and CTA strip - korrespond-guide.php: step-by-step user guide covering mode selection, recipient body presets, output types, tone, all form fields, clarify gate, output columns, and Refine pass - korrespond-tech.php: technical showcase — Hard-RAG pipeline diagram, corpus stats, CITE:N verification system, fine-tuned model details, formal citation style guide, privacy section - 5 UI screenshots + 3 AI-generated hero images in assets/images/korrespond/ - korrespond.php: add "About · User guide · How it works" inline links - preview.php: add korrespond to localizedContent (en+no) and samples; render doc-page links in preview CTA section for korrespond slug - tools.css: +600 lines .kdoc-* CSS (hero, features, pipeline, guide steps, tech diagrams, finetune block, privacy block, stat cards) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
343 lines
21 KiB
PHP
343 lines
21 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
require_once __DIR__ . '/includes/bootstrap.php';
|
|
|
|
$uiLang = dbnToolsCurrentLanguage();
|
|
$isAuthed = dbnToolsIsAuthenticated();
|
|
$langPath = '/korrespond-tech.php';
|
|
$toolsLogin = 'https://dobetternorge.no/tools-login.php?return=' . urlencode('/korrespond.php');
|
|
$registerUrl = 'https://dobetternorge.no/register.php';
|
|
?>
|
|
<!doctype html>
|
|
<html lang="<?= htmlspecialchars($uiLang) ?>">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>How Korrespond works — Hard-RAG, fine-tuned LLM, 3-pass pipeline</title>
|
|
<meta name="description" content="Technical deep-dive: how Korrespond uses Hard-RAG retrieval, a QLoRA fine-tuned legal LLM, and a 3-pass pipeline to produce verified Norwegian legal correspondence.">
|
|
<meta name="robots" content="index, follow">
|
|
<link rel="canonical" href="https://tools.dobetternorge.no/korrespond-tech.php">
|
|
<meta property="og:title" content="How Korrespond works — Hard-RAG, fine-tuned LLM, 3-pass pipeline">
|
|
<meta property="og:description" content="220K+ passages, 8 corpus slices, Azure gpt-4o, citation verification, and a QLoRA fine-tune on Norwegian legal text.">
|
|
<meta name="theme-color" content="#00205B">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Crimson+Pro:wght@400;600;700&family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap">
|
|
<link rel="stylesheet" href="assets/css/tools.css">
|
|
</head>
|
|
<body class="kdoc-page">
|
|
|
|
<header class="lt-nav">
|
|
<a href="https://dobetternorge.no" class="lt-nav__brand">
|
|
<picture>
|
|
<source srcset="assets/images/logo-header.webp" type="image/webp">
|
|
<img class="lt-nav__logo" src="assets/images/logo-header.png" alt="Do Better Norge" width="140" height="36" loading="eager">
|
|
</picture>
|
|
<span class="lt-nav__badge">Legal Tools</span>
|
|
</a>
|
|
<div class="lt-nav__right">
|
|
<nav class="shell-lang-switcher" aria-label="Language">
|
|
<?php foreach (dbnToolsSupportedLanguages() as $langCode): ?>
|
|
<a href="<?= htmlspecialchars($langPath . '?lang=' . $langCode) ?>" class="<?= $langCode === $uiLang ? 'is-active' : '' ?>"><?= htmlspecialchars(dbnToolsLanguageLabel($langCode)) ?></a>
|
|
<?php endforeach; ?>
|
|
</nav>
|
|
<?php if ($isAuthed): ?>
|
|
<a href="/korrespond.php" class="lt-nav__cta lt-nav__cta--enter">Open Korrespond →</a>
|
|
<?php else: ?>
|
|
<a href="<?= htmlspecialchars($toolsLogin) ?>" class="lt-nav__cta">Sign in</a>
|
|
<?php endif; ?>
|
|
</div>
|
|
</header>
|
|
|
|
<nav class="kdoc-doc-nav" aria-label="Korrespond documentation">
|
|
<div class="kdoc-doc-nav__inner">
|
|
<a href="/korrespond-about.php">About</a>
|
|
<a href="/korrespond-guide.php">User guide</a>
|
|
<a href="/korrespond-tech.php" class="is-active">How it works</a>
|
|
<?php if ($isAuthed): ?><a href="/korrespond.php">← Open the tool</a><?php endif; ?>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Hero -->
|
|
<section class="kdoc-hero" style="background: linear-gradient(rgba(5,15,40,0.85),rgba(5,15,40,0.92)), url('assets/images/korrespond/hero-tech.png') center/cover no-repeat;">
|
|
<div class="kdoc-hero__inner">
|
|
<p class="kdoc-hero__kicker">Technical Showcase · Hard-RAG · Fine-tuned LLM · 3-pass pipeline</p>
|
|
<h1 class="kdoc-hero__title">How Korrespond knows what to write.</h1>
|
|
<p class="kdoc-hero__sub">A full walkthrough of the retrieval-augmented generation pipeline, citation verification system, fine-tuned legal model, and the formal citation refine pass that produces court-ready references.</p>
|
|
|
|
<div class="kdoc-hero__stats">
|
|
<div class="kdoc-hero__stat">
|
|
<strong>220K+</strong>
|
|
<span>passages indexed</span>
|
|
</div>
|
|
<div class="kdoc-hero__stat">
|
|
<strong>8</strong>
|
|
<span>corpus slices</span>
|
|
</div>
|
|
<div class="kdoc-hero__stat">
|
|
<strong>3</strong>
|
|
<span>pipeline passes</span>
|
|
</div>
|
|
<div class="kdoc-hero__stat">
|
|
<strong>gpt-4o</strong>
|
|
<span>drafting model</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Architecture overview -->
|
|
<div class="kdoc-section">
|
|
<p class="kdoc-section__eyebrow">Architecture</p>
|
|
<h2 class="kdoc-section__title">Three passes. Each with a distinct job.</h2>
|
|
<p class="kdoc-section__sub">The pipeline is intentionally sequential — Pass 1 is cheap and fast (gpt-4o-mini); Pass 2 is expensive and only runs if the situation is clear enough; Pass 3 is optional and user-triggered.</p>
|
|
|
|
<div class="kdoc-pipeline">
|
|
<div class="kdoc-pipeline__pass">
|
|
<span class="kdoc-pipeline__pass-badge kdoc-pipeline__pass-badge--mini">Pass 1 · gpt-4o-mini</span>
|
|
<h3 class="kdoc-pipeline__pass-title">Classify & gap-check</h3>
|
|
<p class="kdoc-pipeline__pass-body">Parses the intake and returns a structured JSON classification:</p>
|
|
<ul>
|
|
<li><code>summary</code> — one-sentence case summary</li>
|
|
<li><code>parties</code> — identified actors</li>
|
|
<li><code>applicable_acts</code> — relevant statute sets</li>
|
|
<li><code>missing_facts[]</code> — gaps that would hurt draft quality</li>
|
|
<li><code>suggested_goal</code> — inferred goal if none stated</li>
|
|
</ul>
|
|
<p class="kdoc-pipeline__pass-body" style="margin-top:0.7rem;">If <code>missing_facts</code> is non-empty → emits <strong>clarify gate</strong>. No credit deducted until Pass 2 starts.</p>
|
|
</div>
|
|
<div class="kdoc-pipeline__arrow-down" aria-hidden="true">→</div>
|
|
<div class="kdoc-pipeline__pass">
|
|
<span class="kdoc-pipeline__pass-badge">Pass 2 · gpt-4o</span>
|
|
<h3 class="kdoc-pipeline__pass-title">Retrieve → draft → check → translate</h3>
|
|
<p class="kdoc-pipeline__pass-body">Four sub-steps, each verified before proceeding:</p>
|
|
<ul>
|
|
<li><strong>Retrieve:</strong> hybrid dense + BM25 search across the preset corpus slices; top 8 passages returned with source IDs</li>
|
|
<li><strong>Draft:</strong> gpt-4o generates the letter using <code>[CITE:N]</code> tokens referencing only retrieved source IDs</li>
|
|
<li><strong>Self-check:</strong> strips any <code>[CITE:N]</code> token whose source ID isn't in the retrieved pool; flags deadline/goal/tone compliance</li>
|
|
<li><strong>Translate:</strong> Norwegian draft → working language (single call)</li>
|
|
</ul>
|
|
</div>
|
|
<div class="kdoc-pipeline__arrow-down" aria-hidden="true">→</div>
|
|
<div class="kdoc-pipeline__pass">
|
|
<span class="kdoc-pipeline__pass-badge kdoc-pipeline__pass-badge--optional">Pass 3 · optional</span>
|
|
<h3 class="kdoc-pipeline__pass-title">Formal citation refine</h3>
|
|
<p class="kdoc-pipeline__pass-body">User-triggered (+1 credit). Jurisdiction-scoped retrieval, then rewrites inline citations to formal style and appends Rettskilder block:</p>
|
|
<ul>
|
|
<li><strong>Norwegian:</strong> <em>jf. forvaltningsloven § 17</em></li>
|
|
<li><strong>ECHR:</strong> full case name, application number, date, paragraph</li>
|
|
<li><strong>Both:</strong> combined domestic + ECHR grounds</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hard-RAG retrieval -->
|
|
<section class="kdoc-section--alt">
|
|
<div class="kdoc-section">
|
|
<p class="kdoc-section__eyebrow">Hard-RAG</p>
|
|
<h2 class="kdoc-section__title">Every § citation is verified before it reaches you.</h2>
|
|
<p class="kdoc-section__sub">Hard-RAG means the model is constrained to only cite what it retrieved. No § number can appear in the final draft unless a corresponding source passage was actually found and fetched.</p>
|
|
|
|
<div class="kdoc-rag-flow">
|
|
<div class="kdoc-rag-row">
|
|
<div class="kdoc-rag-box kdoc-rag-box--blue">User intake + body preset</div>
|
|
<span class="kdoc-rag-arrow">→</span>
|
|
<div class="kdoc-rag-box">Corpus slice selection</div>
|
|
<span class="kdoc-rag-arrow">→</span>
|
|
<div class="kdoc-rag-box">Hybrid search (dense vector + BM25)</div>
|
|
<span class="kdoc-rag-arrow">→</span>
|
|
<div class="kdoc-rag-box kdoc-rag-box--teal">Top 8 passages with source IDs</div>
|
|
</div>
|
|
<div class="kdoc-rag-row" style="margin-top:8px;">
|
|
<div class="kdoc-rag-box kdoc-rag-box--teal">Passages injected into gpt-4o prompt</div>
|
|
<span class="kdoc-rag-arrow">→</span>
|
|
<div class="kdoc-rag-box">Draft with [CITE:N] tokens only</div>
|
|
<span class="kdoc-rag-arrow">→</span>
|
|
<div class="kdoc-rag-box">Self-check: verify each [CITE:N] resolves</div>
|
|
<span class="kdoc-rag-arrow">→</span>
|
|
<div class="kdoc-rag-box kdoc-rag-box--red">Strip unverified citations</div>
|
|
</div>
|
|
</div>
|
|
|
|
<p style="font-size:0.9rem; line-height:1.7; margin-top:1.5rem; opacity:0.85;">The self-check pass parses every <code>[CITE:N]</code> token in the draft and looks up the source ID <code>N</code> in the retrieved pool. If it doesn't match — the citation is removed and the paragraph is rewritten without it. The output also flags whether the deadline was addressed, whether the stated goal was achieved, and whether the tone matched the selected chip.</p>
|
|
|
|
<h3 style="font-family:'Crimson Pro',serif; font-size:1.2rem; font-weight:700; margin:2rem 0 0.8rem; color:var(--dbn-blue);">What happens when no statute fits?</h3>
|
|
<p style="font-size:0.9rem; line-height:1.7; opacity:0.85;">If no corpus passage closely matches the situation, the draft is produced in plain language without § references. A note in the output says: <em>"No cited law sources — draft is plain-language (no § references available from corpus)."</em> This is the intentional, honest behaviour — a blank draft is better than one with fake citations.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Knowledge base -->
|
|
<div class="kdoc-section">
|
|
<p class="kdoc-section__eyebrow">Knowledge base</p>
|
|
<h2 class="kdoc-section__title">220,000+ passages across 8 corpus slices.</h2>
|
|
<p class="kdoc-section__sub">The legal corpus is split into named slices. Each recipient body preset maps to a set of slices, so retrieval is always scoped to the right area of law.</p>
|
|
|
|
<div class="kdoc-kb-stats">
|
|
<div class="kdoc-kb-stat">
|
|
<strong>220K+</strong>
|
|
<span>total indexed passages</span>
|
|
</div>
|
|
<div class="kdoc-kb-stat">
|
|
<strong>8</strong>
|
|
<span>corpus slices</span>
|
|
</div>
|
|
<div class="kdoc-kb-stat">
|
|
<strong>1,731</strong>
|
|
<span>FNV tribunal decisions</span>
|
|
</div>
|
|
<div class="kdoc-kb-stat">
|
|
<strong>23</strong>
|
|
<span>ECHR Norwegian-family cases</span>
|
|
</div>
|
|
<div class="kdoc-kb-stat">
|
|
<strong>Azure</strong>
|
|
<span>AI Search (West Europe)</span>
|
|
</div>
|
|
<div class="kdoc-kb-stat">
|
|
<strong>Hybrid</strong>
|
|
<span>dense vector + BM25</span>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 style="font-family:'Crimson Pro',serif; font-size:1.15rem; font-weight:700; margin:2rem 0 0.8rem; color:var(--dbn-blue);">Corpus slices</h3>
|
|
<div class="kdoc-slice-grid">
|
|
<span class="kdoc-slice-chip">child_welfare</span>
|
|
<span class="kdoc-slice-chip">echr</span>
|
|
<span class="kdoc-slice-chip">family_core</span>
|
|
<span class="kdoc-slice-chip">bufdir_guidance</span>
|
|
<span class="kdoc-slice-chip">norwegian_courts</span>
|
|
<span class="kdoc-slice-chip">broader_legal</span>
|
|
<span class="kdoc-slice-chip">dbn_resources</span>
|
|
<span class="kdoc-slice-chip">hague</span>
|
|
</div>
|
|
|
|
<h3 style="font-family:'Crimson Pro',serif; font-size:1.15rem; font-weight:700; margin:2rem 0 0.8rem; color:var(--dbn-blue);">Body preset → slice mapping (examples)</h3>
|
|
<table class="kdoc-table">
|
|
<thead>
|
|
<tr><th>Recipient body</th><th>Corpus slices loaded</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td>Barnevernet</td><td>child_welfare · echr · family_core</td></tr>
|
|
<tr><td>Bufdir</td><td>family_core · echr · bufdir_guidance</td></tr>
|
|
<tr><td>NAV</td><td>broader_legal (NAV-loven)</td></tr>
|
|
<tr><td>Skole / Barnehage / SFO</td><td>broader_legal (opplæringslova / barnehageloven)</td></tr>
|
|
<tr><td>Statsforvalteren</td><td>child_welfare · broader_legal</td></tr>
|
|
<tr><td>Trygderetten / Tingretten</td><td>norwegian_courts · broader_legal</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Fine-tuned LLM -->
|
|
<section class="kdoc-section--alt">
|
|
<div class="kdoc-section">
|
|
<p class="kdoc-section__eyebrow">Fine-tuned model</p>
|
|
<h2 class="kdoc-section__title">dbn-legal-agent: trained on Norwegian legal text.</h2>
|
|
|
|
<div class="kdoc-finetune">
|
|
<span class="kdoc-finetune__badge">QLoRA fine-tune</span>
|
|
<h3 class="kdoc-finetune__title">dbn-legal-agent</h3>
|
|
<p class="kdoc-finetune__body">A QLoRA (Quantized Low-Rank Adaptation) fine-tune trained on Norwegian child-welfare and administrative law text. Unlike a general-purpose LLM, dbn-legal-agent has internalized the procedural vocabulary and reasoning patterns of forvaltningsloven: what triggers a § 17 right to be heard, what a lawful § 24 reasoned decision must contain, how barnevernsloven § 6-3 frames the child's best interest standard.</p>
|
|
<p class="kdoc-finetune__body" style="margin-top:0.8rem;">In the Korrespond pipeline, dbn-legal-agent runs as a domain adapter alongside Azure gpt-4o. The retrieval prompt is constructed using dbn-legal-agent's representation of the intake, while gpt-4o handles the final generation within the Hard-RAG constraint. This separation gives structural clarity (gpt-4o) and domain precision (dbn-legal-agent) in the same pipeline.</p>
|
|
<div class="kdoc-finetune__chips">
|
|
<span class="kdoc-finetune__chip">QLoRA</span>
|
|
<span class="kdoc-finetune__chip">forvaltningsloven</span>
|
|
<span class="kdoc-finetune__chip">barnevernsloven</span>
|
|
<span class="kdoc-finetune__chip">child-welfare corpus</span>
|
|
<span class="kdoc-finetune__chip">Norwegian bokmål output</span>
|
|
<span class="kdoc-finetune__chip">gpt-4o co-pipeline</span>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 style="font-family:'Crimson Pro',serif; font-size:1.15rem; font-weight:700; margin:2rem 0 0.8rem; color:var(--dbn-blue);">Model responsibilities in the pipeline</h3>
|
|
<table class="kdoc-table">
|
|
<thead>
|
|
<tr><th>Pass</th><th>Model</th><th>Role</th></tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr><td>Pass 1 classify</td><td>gpt-4o-mini</td><td>Fast structured classification + gap detection</td></tr>
|
|
<tr><td>Pass 1 clarify questions</td><td>gpt-4o-mini + dbn-legal-agent</td><td>Domain-aware question generation</td></tr>
|
|
<tr><td>Pass 2 draft</td><td>gpt-4o</td><td>Full letter generation within Hard-RAG constraints</td></tr>
|
|
<tr><td>Pass 2 self-check</td><td>gpt-4o-mini</td><td>Citation verification + tone/goal/deadline audit</td></tr>
|
|
<tr><td>Pass 2 translate</td><td>gpt-4o-mini</td><td>Norwegian → working language translation</td></tr>
|
|
<tr><td>Pass 3 refine</td><td>gpt-4o</td><td>Formal citation rewrite + Rettskilder block</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Pass 3 formal citations -->
|
|
<div class="kdoc-section">
|
|
<p class="kdoc-section__eyebrow">Pass 3 — Formal citation refine</p>
|
|
<h2 class="kdoc-section__title">Court-ready citations in two styles.</h2>
|
|
<p class="kdoc-section__sub">The optional third pass does a jurisdiction-scoped retrieval run, then rewrites the draft with formal inline citations and a Rettskilder appendix. Two distinct citation formats are supported:</p>
|
|
|
|
<div class="kdoc-features" style="margin-bottom:2rem;">
|
|
<div class="kdoc-feature-card">
|
|
<span class="kdoc-feature-card__icon">🇳🇴</span>
|
|
<h3 class="kdoc-feature-card__title">Norwegian statute style</h3>
|
|
<p class="kdoc-feature-card__body">Inline citations use <em>jf.</em> (with reference to) and the official statute name + section: <code>jf. forvaltningsloven § 17</code>, <code>jf. opplæringslova § 9 A-4</code>, <code>jf. barnevernsloven § 6-3</code>. Section numbers are verified against the corpus before inclusion.</p>
|
|
</div>
|
|
<div class="kdoc-feature-card">
|
|
<span class="kdoc-feature-card__icon">⚖️</span>
|
|
<h3 class="kdoc-feature-card__title">ECHR citation style</h3>
|
|
<p class="kdoc-feature-card__body">Full European Court of Human Rights citation format: case name · application number · date · chamber/Grand Chamber · paragraph. Example: <code>Strand Lobben m.fl. mot Norge, EMD-37283/13 (Storkammer, 10.09.2019), § 207</code>. Sources pulled from the ECHR corpus slice and HUDOC.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 style="font-family:'Crimson Pro',serif; font-size:1.15rem; font-weight:700; margin:0 0 0.8rem; color:var(--dbn-blue);">Example refined output</h3>
|
|
<img src="assets/images/korrespond/output-refined.png" alt="Refined output showing formal citations including opplæringslova §9 A-4 and EMK artikkel 8" class="kdoc-screenshot" style="max-width:800px; display:block;" loading="lazy">
|
|
<p style="font-size:0.82rem; opacity:0.65; text-align:center; margin-top:0.5rem;">Refined draft (Norwegian + English) with <em>opplæringslova § 9 A-4</em> and <em>EMK artikkel 8</em> inline citations.</p>
|
|
|
|
<h3 style="font-family:'Crimson Pro',serif; font-size:1.15rem; font-weight:700; margin:2rem 0 0.8rem; color:var(--dbn-blue);">Anchor queries for ECHR mode</h3>
|
|
<p style="font-size:0.9rem; line-height:1.7; opacity:0.85; margin-bottom:1rem;">For Barnevernet and Bufdir cases, the ECHR refine pass runs specific anchor queries targeting the most-cited Norwegian family cases in the HUDOC corpus:</p>
|
|
<div class="kdoc-slice-grid">
|
|
<span class="kdoc-slice-chip">Strand Lobben m.fl. mot Norge</span>
|
|
<span class="kdoc-slice-chip">Johansen mot Norge</span>
|
|
<span class="kdoc-slice-chip">K.O. og V.M. mot Norge</span>
|
|
<span class="kdoc-slice-chip">Aune mot Norge</span>
|
|
<span class="kdoc-slice-chip">EMK Art. 8 family life Norway</span>
|
|
<span class="kdoc-slice-chip">EMK Art. 6 fair trial</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Privacy by design -->
|
|
<section class="kdoc-section--alt">
|
|
<div class="kdoc-section">
|
|
<p class="kdoc-section__eyebrow">Privacy & security</p>
|
|
<h2 class="kdoc-section__title">Your documents never leave your session.</h2>
|
|
|
|
<div class="kdoc-privacy">
|
|
<p class="kdoc-privacy__title">Privacy by design</p>
|
|
<ul>
|
|
<li>All uploaded files are extracted to text <strong>in memory</strong> using PHP's in-process file handlers. The raw binary is never written to disk on the server.</li>
|
|
<li>Session context (your narrative, uploaded text, drafts) is scoped to your authenticated session and discarded when the session ends.</li>
|
|
<li>Azure OpenAI (gpt-4o, gpt-4o-mini) is configured on the <strong>West Europe</strong> region. Data processed via Azure OpenAI is not used for model training under the default enterprise agreement.</li>
|
|
<li>Azure AI Search (<code>bnl-legal-search</code>) stores <strong>only the public legal corpus</strong> — statutes, tribunal decisions, ECHR judgments. None of your case information is stored in the search index.</li>
|
|
<li>Qdrant vector database stores only the public corpus embeddings — no user data.</li>
|
|
<li>Telemetry logged: tool name, language, output type, pass count, latency, source count. <strong>No case text, no names, no case references are logged.</strong></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- CTA -->
|
|
<section class="kdoc-cta-strip">
|
|
<h2 class="kdoc-cta-strip__title">See it work on your case.</h2>
|
|
<p class="kdoc-cta-strip__sub">Free for Do Better Norge members. All 3 passes available to every member.</p>
|
|
<div class="kdoc-hero__ctas">
|
|
<?php if ($isAuthed): ?>
|
|
<a href="/korrespond.php" class="kdoc-btn-primary">Open Korrespond →</a>
|
|
<?php else: ?>
|
|
<a href="<?= htmlspecialchars($toolsLogin) ?>" class="kdoc-btn-primary">Sign in to use Korrespond →</a>
|
|
<a href="<?= htmlspecialchars($registerUrl) ?>" class="kdoc-btn-secondary">Register free</a>
|
|
<?php endif; ?>
|
|
<a href="/korrespond-guide.php" class="kdoc-btn-secondary">User guide</a>
|
|
</div>
|
|
</section>
|
|
|
|
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
|
<script src="assets/js/tools.js" defer></script>
|
|
</body>
|
|
</html>
|