feat(workbench): add DBN Case Workbench guided case-preparation hub
Additive-only change: new workbench.php authenticated page with guided intake flow, evidence map, tool sequence, output checklist, and sessionStorage-only note persistence. Dashboard and public index get a new Case Workbench card. No existing tools, APIs, or prompts modified. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6053,6 +6053,329 @@ body.lt-landing {
|
||||
.adv-preview-btn { font-size:.82rem; padding:5px 14px; border:1px solid var(--line); border-radius:5px; background:var(--bg); color:var(--muted); cursor:pointer; }
|
||||
.adv-preview-btn:hover { border-color:var(--teal); color:var(--teal); background:var(--soft-teal); }
|
||||
|
||||
/* Case Workbench - additive page */
|
||||
.workbench-shell {
|
||||
max-width: 1480px;
|
||||
}
|
||||
|
||||
.workbench-dashboard-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.workbench-entry-art {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(0, 32, 91, 0.96), rgba(15, 118, 110, 0.86)),
|
||||
repeating-linear-gradient(90deg, rgba(255,255,255,0.12) 0 1px, transparent 1px 28px);
|
||||
color: #fff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.workbench-entry-art span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 84px;
|
||||
height: 84px;
|
||||
border: 2px solid rgba(255,255,255,0.55);
|
||||
border-radius: 50%;
|
||||
font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
background: rgba(255,255,255,0.10);
|
||||
}
|
||||
|
||||
.workbench-hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.5fr) minmax(280px, 0.7fr);
|
||||
gap: 18px;
|
||||
align-items: stretch;
|
||||
margin: 22px 0 18px;
|
||||
}
|
||||
|
||||
.workbench-hero__copy,
|
||||
.workbench-hero__panel,
|
||||
.workbench-panel {
|
||||
border: 1px solid var(--dbn-line);
|
||||
background: rgba(255,255,255,0.72);
|
||||
box-shadow: 0 18px 45px rgba(25, 35, 52, 0.08);
|
||||
}
|
||||
|
||||
.workbench-hero__copy {
|
||||
padding: clamp(24px, 4vw, 44px);
|
||||
}
|
||||
|
||||
.workbench-hero__copy h2 {
|
||||
max-width: 860px;
|
||||
margin: 0;
|
||||
color: var(--dbn-ink);
|
||||
font-family: 'Crimson Pro', serif;
|
||||
font-size: clamp(2rem, 4vw, 4.4rem);
|
||||
line-height: 0.94;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.workbench-hero__copy p:last-child {
|
||||
max-width: 760px;
|
||||
margin: 16px 0 0;
|
||||
color: rgba(22, 19, 15, 0.74);
|
||||
font-size: 1rem;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.workbench-kicker {
|
||||
margin: 0 0 10px;
|
||||
color: var(--dbn-red);
|
||||
font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.09em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.workbench-hero__panel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 24px;
|
||||
border-left: 5px solid var(--dbn-teal);
|
||||
}
|
||||
|
||||
.workbench-hero__panel strong {
|
||||
color: var(--dbn-blue);
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
|
||||
.workbench-hero__panel p {
|
||||
margin: 0;
|
||||
color: rgba(22, 19, 15, 0.70);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.workbench-clear,
|
||||
.workbench-tool-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 42px;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
padding: 0 16px;
|
||||
background: var(--dbn-blue);
|
||||
color: #fff;
|
||||
font-weight: 800;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.workbench-clear {
|
||||
align-self: flex-start;
|
||||
background: var(--dbn-red);
|
||||
}
|
||||
|
||||
.workbench-status {
|
||||
min-height: 20px;
|
||||
color: var(--dbn-teal);
|
||||
font-size: 0.86rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.workbench-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
|
||||
gap: 18px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.workbench-panel {
|
||||
padding: clamp(18px, 2.4vw, 28px);
|
||||
}
|
||||
|
||||
.workbench-panel--flow,
|
||||
.workbench-panel--outputs {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.workbench-section-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 18px;
|
||||
border-bottom: 1px solid rgba(22, 19, 15, 0.12);
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
.workbench-section-head h2 {
|
||||
margin: 0;
|
||||
color: var(--dbn-ink);
|
||||
font-family: 'Crimson Pro', serif;
|
||||
font-size: clamp(1.45rem, 2vw, 2.1rem);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.workbench-form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.workbench-field {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
color: var(--dbn-ink);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.workbench-field--wide {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.workbench-field span {
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.workbench-field input,
|
||||
.workbench-field select,
|
||||
.workbench-field textarea {
|
||||
width: 100%;
|
||||
border: 1px solid rgba(22, 19, 15, 0.18);
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
color: var(--dbn-ink);
|
||||
font: inherit;
|
||||
font-weight: 500;
|
||||
padding: 12px 13px;
|
||||
}
|
||||
|
||||
.workbench-field textarea {
|
||||
min-height: 112px;
|
||||
resize: vertical;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.workbench-field input:focus,
|
||||
.workbench-field select:focus,
|
||||
.workbench-field textarea:focus,
|
||||
.workbench-clear:focus,
|
||||
.workbench-tool-link:focus,
|
||||
.workbench-check input:focus {
|
||||
outline: 3px solid rgba(15, 118, 110, 0.26);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.workbench-evidence-list {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.workbench-flow {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, minmax(160px, 1fr));
|
||||
gap: 12px;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.workbench-step {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
min-height: 276px;
|
||||
border: 1px solid rgba(0, 32, 91, 0.14);
|
||||
background: #fff;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.workbench-step__index {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
background: var(--dbn-blue);
|
||||
color: #fff;
|
||||
font-family: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.workbench-step__body {
|
||||
padding: 16px 0;
|
||||
}
|
||||
|
||||
.workbench-step__body p,
|
||||
.workbench-step__body span {
|
||||
margin: 0;
|
||||
color: rgba(22, 19, 15, 0.66);
|
||||
font-size: 0.86rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.workbench-step__body h3 {
|
||||
margin: 6px 0 10px;
|
||||
color: var(--dbn-ink);
|
||||
font-size: 1.08rem;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.workbench-tool-link {
|
||||
min-height: 38px;
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
.workbench-checklist {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.workbench-check {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
min-height: 72px;
|
||||
border: 1px solid rgba(0, 32, 91, 0.14);
|
||||
background: #fff;
|
||||
padding: 14px;
|
||||
color: var(--dbn-ink);
|
||||
font-weight: 800;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.workbench-check input {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 1px;
|
||||
accent-color: var(--dbn-teal);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.workbench-hero,
|
||||
.workbench-grid,
|
||||
.workbench-checklist {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.workbench-flow {
|
||||
grid-template-columns: repeat(7, minmax(220px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.workbench-form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.workbench-section-head {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print styles */
|
||||
@media print {
|
||||
.tool-rail, .reasoning-panel, .topbar, .tool-form,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const form = document.getElementById('workbenchForm');
|
||||
const clearButton = document.getElementById('workbenchClear');
|
||||
const status = document.getElementById('workbenchStatus');
|
||||
const storageKey = 'dbn.caseWorkbench.v1';
|
||||
let statusTimer = 0;
|
||||
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
|
||||
function setStatus(message) {
|
||||
if (!status) {
|
||||
return;
|
||||
}
|
||||
status.textContent = message;
|
||||
window.clearTimeout(statusTimer);
|
||||
statusTimer = window.setTimeout(function () {
|
||||
status.textContent = '';
|
||||
}, 2200);
|
||||
}
|
||||
|
||||
function collect() {
|
||||
const data = {};
|
||||
const fields = form.querySelectorAll('input, select, textarea');
|
||||
fields.forEach(function (field) {
|
||||
if (!field.name) {
|
||||
return;
|
||||
}
|
||||
if (field.type === 'checkbox') {
|
||||
if (!Array.isArray(data[field.name])) {
|
||||
data[field.name] = [];
|
||||
}
|
||||
if (field.checked) {
|
||||
data[field.name].push(field.value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
data[field.name] = field.value;
|
||||
});
|
||||
data.saved_at = new Date().toISOString();
|
||||
return data;
|
||||
}
|
||||
|
||||
function restore(data) {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return;
|
||||
}
|
||||
const fields = form.querySelectorAll('input, select, textarea');
|
||||
fields.forEach(function (field) {
|
||||
if (!field.name) {
|
||||
return;
|
||||
}
|
||||
if (field.type === 'checkbox') {
|
||||
const values = Array.isArray(data[field.name]) ? data[field.name] : [];
|
||||
field.checked = values.indexOf(field.value) !== -1;
|
||||
return;
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(data, field.name)) {
|
||||
field.value = data[field.name] || '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function save() {
|
||||
try {
|
||||
window.sessionStorage.setItem(storageKey, JSON.stringify(collect()));
|
||||
setStatus('Saved in this browser session.');
|
||||
} catch (error) {
|
||||
setStatus('Could not save this session locally.');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const raw = window.sessionStorage.getItem(storageKey);
|
||||
if (raw) {
|
||||
restore(JSON.parse(raw));
|
||||
setStatus('Restored local workbench notes.');
|
||||
}
|
||||
} catch (error) {
|
||||
window.sessionStorage.removeItem(storageKey);
|
||||
}
|
||||
|
||||
form.addEventListener('input', save);
|
||||
form.addEventListener('change', save);
|
||||
|
||||
if (clearButton) {
|
||||
clearButton.addEventListener('click', function () {
|
||||
window.sessionStorage.removeItem(storageKey);
|
||||
form.reset();
|
||||
setStatus('Workbench session cleared.');
|
||||
});
|
||||
}
|
||||
}());
|
||||
@@ -10,6 +10,7 @@ if (!dbnToolsIsAuthenticated()) {
|
||||
|
||||
$uiLang = dbnToolsCurrentLanguage();
|
||||
$tools = dbnToolsLaunchedTools($uiLang);
|
||||
$workbench = dbnToolsWorkbenchMeta($uiLang);
|
||||
$langPath = '/dashboard.php';
|
||||
|
||||
require_once __DIR__ . '/includes/tool-svgs.php';
|
||||
@@ -73,6 +74,13 @@ window.DBN_TOOLS_LANG = <?= json_encode($uiLang, JSON_UNESCAPED_UNICODE) ?>;
|
||||
<div class="disclaimer" role="note"><?= htmlspecialchars(dbnToolsT('disclaimer', $uiLang)) ?></div>
|
||||
|
||||
<section class="tool-dashboard-grid" aria-label="Available tools">
|
||||
<a class="dashboard-tool-card dashboard-tool-card--workbench" href="<?= htmlspecialchars($workbench['url']) ?>">
|
||||
<span class="dashboard-tool-card__icon"><?= htmlspecialchars($workbench['icon']) ?></span>
|
||||
<span class="dashboard-tool-card__badge"><?= htmlspecialchars($workbench['badge']) ?></span>
|
||||
<h2><?= htmlspecialchars($workbench['label']) ?></h2>
|
||||
<p><?= htmlspecialchars($workbench['description']) ?></p>
|
||||
<strong><?= htmlspecialchars(dbnToolsT('enter_workbench', $uiLang)) ?></strong>
|
||||
</a>
|
||||
<?php foreach ($tools as $slug => $item): ?>
|
||||
<a class="dashboard-tool-card" href="<?= htmlspecialchars($item['url']) ?>">
|
||||
<span class="dashboard-tool-card__icon"><?= htmlspecialchars($item['icon']) ?></span>
|
||||
|
||||
@@ -318,6 +318,121 @@ function dbnToolsT(string $key, ?string $language = null): string
|
||||
return (string)($all[$language][$key] ?? $all['en'][$key] ?? $key);
|
||||
}
|
||||
|
||||
function dbnToolsWorkbenchCopy(?string $language = null): array
|
||||
{
|
||||
$language = dbnToolsNormalizeUiLanguage($language ?? dbnToolsCurrentLanguage());
|
||||
$copy = [
|
||||
'en' => [
|
||||
'title' => 'Case Workbench',
|
||||
'label' => 'Case Workbench',
|
||||
'sub' => 'Guided case flow',
|
||||
'description' => 'Plan a case, map evidence, and open the right legal tools in sequence without storing documents.',
|
||||
'badge' => 'Private session',
|
||||
'kicker' => 'Guided preparation',
|
||||
'hero_title' => 'One calm place to organise the next legal step.',
|
||||
'hero_text' => 'Use this workbench to frame the case, decide which tool to open next, and keep a local evidence map while your documents stay in memory by default.',
|
||||
'privacy_title' => 'Local notes only',
|
||||
'privacy_text' => 'This page saves notes in browser sessionStorage. It does not upload files or call tool APIs.',
|
||||
'clear_session' => 'Clear session',
|
||||
'all_tools' => 'All tools',
|
||||
'intake_title' => 'Case intake',
|
||||
'evidence_title' => 'Evidence map',
|
||||
'flow_title' => 'Recommended tool sequence',
|
||||
'outputs_title' => 'Output checklist',
|
||||
'role' => 'Who are you helping?',
|
||||
'choose' => 'Choose...',
|
||||
'role_parent' => 'Parent',
|
||||
'role_family' => 'Family member',
|
||||
'role_advocate' => 'Advocate or lawyer',
|
||||
'role_supporter' => 'Supporter',
|
||||
'case_type' => 'Case type',
|
||||
'case_barnevernet' => 'Barnevernet / child welfare',
|
||||
'case_custody' => 'Custody or residence',
|
||||
'case_access' => 'Contact / samvær',
|
||||
'case_echr' => 'ECHR / Strasbourg preparation',
|
||||
'deadline' => 'Next deadline',
|
||||
'language' => 'Working language',
|
||||
'main_concern' => 'Main concern',
|
||||
'main_concern_hint' => 'What decision, meeting, deadline, or document are you preparing for?',
|
||||
'documents' => 'Documents',
|
||||
'documents_hint' => 'List letters, decisions, reports, emails, or forms.',
|
||||
'meetings' => 'Meetings and audio',
|
||||
'meetings_hint' => 'List recordings, meeting notes, or conversations to transcribe.',
|
||||
'dates' => 'Dates and deadlines',
|
||||
'dates_hint' => 'List hearings, response deadlines, visits, decisions, and events.',
|
||||
'claims' => 'Claims and facts',
|
||||
'claims_hint' => 'List what the other side says, what you dispute, and what you can prove.',
|
||||
'missing' => 'Missing proof',
|
||||
'missing_hint' => 'List records, witnesses, messages, or sources still needed.',
|
||||
'bring_redact' => 'Bring back: safe text you can share with helpers.',
|
||||
'bring_transcribe' => 'Bring back: speaker notes, quotes, and follow-up issues.',
|
||||
'bring_timeline' => 'Bring back: dated events and urgent deadlines.',
|
||||
'bring_barnevernet' => 'Bring back: red flags, parties, and cited procedural issues.',
|
||||
'bring_research' => 'Bring back: legal angles, source excerpts, and uncertainty notes.',
|
||||
'bring_advocate' => 'Bring back: strongest brief sections and weak points.',
|
||||
'bring_corpus' => 'Bring back: source titles, sections, and exact citation context.',
|
||||
'open_tool' => 'Open tool',
|
||||
'output_lawyer' => 'Lawyer handoff pack',
|
||||
'output_barnevernet_response' => 'Barnevernet response preparation',
|
||||
'output_meeting_prep' => 'Meeting preparation note',
|
||||
'output_strasbourg' => 'Strasbourg / ECHR research prep',
|
||||
'next_step' => 'Next practical step',
|
||||
'next_step_hint' => 'Write the next action, owner, and deadline.',
|
||||
],
|
||||
'no' => [
|
||||
'title' => 'Saksarbeidsbenk',
|
||||
'label' => 'Saksarbeidsbenk',
|
||||
'sub' => 'Veiledet saksflyt',
|
||||
'description' => 'Planlegg saken, kartlegg bevis og åpne riktig verktøy i riktig rekkefølge uten å lagre dokumenter.',
|
||||
'badge' => 'Privat økt',
|
||||
'kicker' => 'Veiledet forberedelse',
|
||||
'hero_title' => 'Ett rolig sted for neste juridiske steg.',
|
||||
'hero_text' => 'Bruk arbeidsbenken til å ramme inn saken, velge neste verktøy og holde et lokalt beviskart mens dokumenter behandles i minnet som standard.',
|
||||
'privacy_title' => 'Kun lokale notater',
|
||||
'privacy_text' => 'Denne siden lagrer notater i nettleserens sessionStorage. Den laster ikke opp filer og kaller ikke verktøy-API-er.',
|
||||
],
|
||||
'uk' => [
|
||||
'title' => 'Робочий простір справи',
|
||||
'label' => 'Робочий простір справи',
|
||||
'sub' => 'Керований шлях справи',
|
||||
'description' => 'Плануйте справу, картуйте докази й відкривайте потрібні інструменти по черзі без збереження документів.',
|
||||
'badge' => 'Приватна сесія',
|
||||
'kicker' => 'Керована підготовка',
|
||||
'hero_title' => 'Одне спокійне місце для наступного юридичного кроку.',
|
||||
'hero_text' => 'Використовуйте цей простір, щоб описати справу, вибрати наступний інструмент і вести локальну карту доказів.',
|
||||
'privacy_title' => 'Лише локальні нотатки',
|
||||
'privacy_text' => 'Сторінка зберігає нотатки в sessionStorage браузера. Вона не завантажує файли і не викликає API інструментів.',
|
||||
],
|
||||
'pl' => [
|
||||
'title' => 'Panel pracy nad sprawą',
|
||||
'label' => 'Panel pracy nad sprawą',
|
||||
'sub' => 'Prowadzony tok sprawy',
|
||||
'description' => 'Zaplanuj sprawę, uporządkuj dowody i otwieraj właściwe narzędzia po kolei bez zapisywania dokumentów.',
|
||||
'badge' => 'Prywatna sesja',
|
||||
'kicker' => 'Prowadzone przygotowanie',
|
||||
'hero_title' => 'Jedno spokojne miejsce na kolejny krok prawny.',
|
||||
'hero_text' => 'Użyj panelu, aby opisać sprawę, wybrać następne narzędzie i prowadzić lokalną mapę dowodów.',
|
||||
'privacy_title' => 'Tylko lokalne notatki',
|
||||
'privacy_text' => 'Strona zapisuje notatki w sessionStorage przeglądarki. Nie przesyła plików i nie wywołuje API narzędzi.',
|
||||
],
|
||||
];
|
||||
|
||||
return array_merge($copy['en'], $copy[$language] ?? []);
|
||||
}
|
||||
|
||||
function dbnToolsWorkbenchMeta(?string $language = null): array
|
||||
{
|
||||
$copy = dbnToolsWorkbenchCopy($language);
|
||||
return [
|
||||
'label' => $copy['label'],
|
||||
'sub' => $copy['sub'],
|
||||
'description' => $copy['description'],
|
||||
'badge' => $copy['badge'],
|
||||
'url' => 'workbench.php',
|
||||
'icon' => 'WB',
|
||||
];
|
||||
}
|
||||
|
||||
function dbnToolsLaunchedTools(?string $language = null): array
|
||||
{
|
||||
$language = dbnToolsNormalizeUiLanguage($language ?? dbnToolsCurrentLanguage());
|
||||
|
||||
@@ -47,6 +47,7 @@ if (isset($_GET['sso']) && !dbnToolsIsAuthenticated()) {
|
||||
$isAuthed = dbnToolsIsAuthenticated();
|
||||
$userEmail = $isAuthed ? (string)($_SESSION['dbn_tools_user_email'] ?? '') : '';
|
||||
$tools = dbnToolsLaunchedTools($uiLang);
|
||||
$workbench = dbnToolsWorkbenchMeta($uiLang);
|
||||
$langPath = '/';
|
||||
$toolsLogin = 'https://dobetternorge.no/tools-login.php?return=' . urlencode($returnPath);
|
||||
$registerUrl = 'https://dobetternorge.no/register.php';
|
||||
@@ -147,6 +148,17 @@ window.DBN_TOOLS_LANG = <?= json_encode($uiLang, JSON_UNESCAPED_UNICODE) ?>;
|
||||
<p class="lt-tools__sub">Seven purpose-built AI tools — from audio transcription to deep legal research. Every tool processes your documents in memory and discards them when you’re done.</p>
|
||||
</div>
|
||||
<div class="lt-grid">
|
||||
<a class="lt-card lt-card--workbench" href="<?= $isAuthed ? '/workbench.php' : '#access' ?>" data-tool="workbench">
|
||||
<div class="lt-card__art workbench-entry-art" aria-hidden="true">
|
||||
<span>WB</span>
|
||||
</div>
|
||||
<div class="lt-card__body">
|
||||
<p class="lt-card__badge"><?= htmlspecialchars($workbench['badge']) ?></p>
|
||||
<h3 class="lt-card__title"><?= htmlspecialchars($workbench['label']) ?></h3>
|
||||
<p class="lt-card__desc"><?= htmlspecialchars($workbench['description']) ?></p>
|
||||
<span class="lt-card__arrow"><?= htmlspecialchars($isAuthed ? dbnToolsT('enter_workbench', $uiLang) : dbnToolsT('primary_access', $uiLang)) ?> →</span>
|
||||
</div>
|
||||
</a>
|
||||
<?php foreach ($tools as $slug => $item): ?>
|
||||
<a class="lt-card" href="preview.php?tool=<?= htmlspecialchars($slug) ?>" data-tool="<?= htmlspecialchars($slug) ?>">
|
||||
<div class="lt-card__art" aria-hidden="true">
|
||||
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/includes/bootstrap.php';
|
||||
|
||||
if (!dbnToolsIsAuthenticated()) {
|
||||
header('Location: /?return=' . urlencode($_SERVER['REQUEST_URI'] ?? '/workbench.php'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$uiLang = dbnToolsCurrentLanguage();
|
||||
$tools = dbnToolsLaunchedTools($uiLang);
|
||||
$copy = dbnToolsWorkbenchCopy($uiLang);
|
||||
$langPath = '/workbench.php';
|
||||
|
||||
$toolFlow = [
|
||||
['slug' => 'transcribe', 'step' => '01', 'bring' => $copy['bring_transcribe']],
|
||||
['slug' => 'timeline', 'step' => '02', 'bring' => $copy['bring_timeline']],
|
||||
['slug' => 'redact', 'step' => '03', 'bring' => $copy['bring_redact']],
|
||||
['slug' => 'barnevernet', 'step' => '04', 'bring' => $copy['bring_barnevernet']],
|
||||
['slug' => 'advocate', 'step' => '05', 'bring' => $copy['bring_advocate']],
|
||||
['slug' => 'deep-research', 'step' => '06', 'bring' => $copy['bring_research']],
|
||||
['slug' => 'corpus', 'step' => '07', 'bring' => $copy['bring_corpus']],
|
||||
];
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="<?= htmlspecialchars($uiLang) ?>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= htmlspecialchars($copy['title']) ?> - Do Better Norge</title>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<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&display=swap">
|
||||
<link rel="stylesheet" href="assets/css/tools.css">
|
||||
</head>
|
||||
<body data-authenticated="true" class="lt-app workbench-page">
|
||||
<script>
|
||||
window.DBN_TOOLS_AUTHENTICATED = true;
|
||||
window.DBN_TOOLS_LANG = <?= json_encode($uiLang, JSON_UNESCAPED_UNICODE) ?>;
|
||||
</script>
|
||||
|
||||
<main id="appShell" class="app-shell workbench-shell">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow"><?= htmlspecialchars(dbnToolsT('brand_line', $uiLang)) ?></p>
|
||||
<h1><?= htmlspecialchars($copy['title']) ?></h1>
|
||||
<div class="case-no">
|
||||
<span class="pulse"></span>
|
||||
<span>family-legal</span>
|
||||
<span class="case-sep">.</span>
|
||||
<span><?= htmlspecialchars(dbnToolsT('retention', $uiLang)) ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="topbar-actions">
|
||||
<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>
|
||||
<a class="secondary-button workbench-dashboard-link" href="/dashboard.php"><?= htmlspecialchars($copy['all_tools']) ?></a>
|
||||
<span id="healthPill" class="status-pill"><?= htmlspecialchars(dbnToolsT('session_active', $uiLang)) ?></span>
|
||||
<button id="healthButton" class="secondary-button" type="button"><?= htmlspecialchars(dbnToolsT('health', $uiLang)) ?></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="workbench-hero" aria-labelledby="workbenchHeroTitle">
|
||||
<div class="workbench-hero__copy">
|
||||
<p class="workbench-kicker"><?= htmlspecialchars($copy['kicker']) ?></p>
|
||||
<h2 id="workbenchHeroTitle"><?= htmlspecialchars($copy['hero_title']) ?></h2>
|
||||
<p><?= htmlspecialchars($copy['hero_text']) ?></p>
|
||||
</div>
|
||||
<div class="workbench-hero__panel" aria-label="<?= htmlspecialchars($copy['privacy_title']) ?>">
|
||||
<strong><?= htmlspecialchars($copy['privacy_title']) ?></strong>
|
||||
<p><?= htmlspecialchars($copy['privacy_text']) ?></p>
|
||||
<button id="workbenchClear" class="workbench-clear" type="button"><?= htmlspecialchars($copy['clear_session']) ?></button>
|
||||
<p id="workbenchStatus" class="workbench-status" role="status" aria-live="polite"></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="disclaimer" role="note"><?= htmlspecialchars(dbnToolsT('disclaimer', $uiLang)) ?></div>
|
||||
|
||||
<form id="workbenchForm" class="workbench-grid" autocomplete="off">
|
||||
<section class="workbench-panel workbench-panel--intake" aria-labelledby="workbenchIntakeTitle">
|
||||
<div class="workbench-section-head">
|
||||
<p class="workbench-kicker">01</p>
|
||||
<h2 id="workbenchIntakeTitle"><?= htmlspecialchars($copy['intake_title']) ?></h2>
|
||||
</div>
|
||||
<div class="workbench-form-grid">
|
||||
<label class="workbench-field" for="wbRole">
|
||||
<span><?= htmlspecialchars($copy['role']) ?></span>
|
||||
<select id="wbRole" name="role">
|
||||
<option value=""><?= htmlspecialchars($copy['choose']) ?></option>
|
||||
<option><?= htmlspecialchars($copy['role_parent']) ?></option>
|
||||
<option><?= htmlspecialchars($copy['role_family']) ?></option>
|
||||
<option><?= htmlspecialchars($copy['role_advocate']) ?></option>
|
||||
<option><?= htmlspecialchars($copy['role_supporter']) ?></option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="workbench-field" for="wbCaseType">
|
||||
<span><?= htmlspecialchars($copy['case_type']) ?></span>
|
||||
<select id="wbCaseType" name="case_type">
|
||||
<option value=""><?= htmlspecialchars($copy['choose']) ?></option>
|
||||
<option><?= htmlspecialchars($copy['case_barnevernet']) ?></option>
|
||||
<option><?= htmlspecialchars($copy['case_custody']) ?></option>
|
||||
<option><?= htmlspecialchars($copy['case_access']) ?></option>
|
||||
<option><?= htmlspecialchars($copy['case_echr']) ?></option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="workbench-field" for="wbDeadline">
|
||||
<span><?= htmlspecialchars($copy['deadline']) ?></span>
|
||||
<input id="wbDeadline" name="deadline" type="date">
|
||||
</label>
|
||||
<label class="workbench-field" for="wbLanguage">
|
||||
<span><?= htmlspecialchars($copy['language']) ?></span>
|
||||
<select id="wbLanguage" name="language">
|
||||
<option>English</option>
|
||||
<option>Norwegian</option>
|
||||
<option>Ukrainian</option>
|
||||
<option>Polish</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<label class="workbench-field workbench-field--wide" for="wbConcern">
|
||||
<span><?= htmlspecialchars($copy['main_concern']) ?></span>
|
||||
<textarea id="wbConcern" name="main_concern" rows="5" placeholder="<?= htmlspecialchars($copy['main_concern_hint']) ?>"></textarea>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
<section class="workbench-panel workbench-panel--evidence" aria-labelledby="workbenchEvidenceTitle">
|
||||
<div class="workbench-section-head">
|
||||
<p class="workbench-kicker">02</p>
|
||||
<h2 id="workbenchEvidenceTitle"><?= htmlspecialchars($copy['evidence_title']) ?></h2>
|
||||
</div>
|
||||
<div class="workbench-evidence-list">
|
||||
<?php
|
||||
$evidenceFields = [
|
||||
['documents', $copy['documents'], $copy['documents_hint']],
|
||||
['meetings', $copy['meetings'], $copy['meetings_hint']],
|
||||
['dates', $copy['dates'], $copy['dates_hint']],
|
||||
['claims', $copy['claims'], $copy['claims_hint']],
|
||||
['missing', $copy['missing'], $copy['missing_hint']],
|
||||
];
|
||||
foreach ($evidenceFields as [$name, $label, $hint]):
|
||||
$id = 'wb' . ucfirst($name);
|
||||
?>
|
||||
<label class="workbench-field" for="<?= htmlspecialchars($id) ?>">
|
||||
<span><?= htmlspecialchars($label) ?></span>
|
||||
<textarea id="<?= htmlspecialchars($id) ?>" name="<?= htmlspecialchars($name) ?>" rows="4" placeholder="<?= htmlspecialchars($hint) ?>"></textarea>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="workbench-panel workbench-panel--flow" aria-labelledby="workbenchFlowTitle">
|
||||
<div class="workbench-section-head">
|
||||
<p class="workbench-kicker">03</p>
|
||||
<h2 id="workbenchFlowTitle"><?= htmlspecialchars($copy['flow_title']) ?></h2>
|
||||
</div>
|
||||
<div class="workbench-flow">
|
||||
<?php foreach ($toolFlow as $item):
|
||||
$tool = $tools[$item['slug']] ?? null;
|
||||
if (!$tool) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<article class="workbench-step">
|
||||
<div class="workbench-step__index"><?= htmlspecialchars($item['step']) ?></div>
|
||||
<div class="workbench-step__body">
|
||||
<p><?= htmlspecialchars($tool['sub']) ?></p>
|
||||
<h3><?= htmlspecialchars($tool['label']) ?></h3>
|
||||
<span><?= htmlspecialchars($item['bring']) ?></span>
|
||||
</div>
|
||||
<a class="workbench-tool-link" href="<?= htmlspecialchars($tool['url']) ?>"><?= htmlspecialchars($copy['open_tool']) ?></a>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="workbench-panel workbench-panel--outputs" aria-labelledby="workbenchOutputsTitle">
|
||||
<div class="workbench-section-head">
|
||||
<p class="workbench-kicker">04</p>
|
||||
<h2 id="workbenchOutputsTitle"><?= htmlspecialchars($copy['outputs_title']) ?></h2>
|
||||
</div>
|
||||
<div class="workbench-checklist">
|
||||
<?php foreach (['lawyer', 'barnevernet_response', 'meeting_prep', 'strasbourg'] as $outputKey): ?>
|
||||
<label class="workbench-check">
|
||||
<input type="checkbox" name="outputs[]" value="<?= htmlspecialchars($outputKey) ?>">
|
||||
<span><?= htmlspecialchars($copy['output_' . $outputKey]) ?></span>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<label class="workbench-field workbench-field--wide" for="wbNextStep">
|
||||
<span><?= htmlspecialchars($copy['next_step']) ?></span>
|
||||
<textarea id="wbNextStep" name="next_step" rows="4" placeholder="<?= htmlspecialchars($copy['next_step_hint']) ?>"></textarea>
|
||||
</label>
|
||||
</section>
|
||||
</form>
|
||||
</main>
|
||||
<?php require_once __DIR__ . '/includes/footer.php'; ?>
|
||||
<script src="assets/js/tools.js" defer></script>
|
||||
<script src="assets/js/workbench.js" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user