Page polish + print styles + skeletons + responsive

Case view:
- Header card gets gold right-border, serif display title, pill-style
  action links with gold hover
- Document groups: gold-underlined section headers, hover rows with
  parchment background

Wizard (new case):
- Step tabs in display font with separators; active/done states use
  navy/success colors with proper background contrast
- Nav buttons separated by hairline divider

Skills page:
- Pill badges for ok/warn, gold icon, hover elevation

Upload zone:
- Larger dashed border, serif header, gold-wash hover state

Loading skeletons:
- .skeleton / .skeleton-line classes with shimmer animation
- Case list shows 3 skeleton cards while loading
- Style report shows skeleton hero while loading

Empty states:
- Case list gets ornamental ❦ + emotional copy in display font
- Error messages include underlying error detail

Print stylesheet:
- Hides header/nav/sidebar/buttons
- Forces card borders and page-break-inside for portrait printing
- Expands details elements so content prints

Responsive:
- Mobile: simplified hero, narrower process panel, wrapping header
- Tablet: home grid collapses to single column

Fixes:
- DONUT_COLORS reverts to hex literals (was var(--color-gold) string
  which doesn't interpolate in conic-gradient reliably across browsers)
- Sig-phrase headline now prefers clean phrases over template-heavy ones

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 12:07:42 +00:00
parent 3e0221ccec
commit ea3ef5963e
2 changed files with 325 additions and 58 deletions

View File

@@ -242,3 +242,80 @@ a:hover { color: var(--color-gold); }
);
margin: var(--space-6) 0;
}
/* ── Loading skeleton ───────────────────────────────── */
.skeleton {
background: linear-gradient(
100deg,
var(--color-cream-deep) 30%,
var(--color-parchment) 50%,
var(--color-cream-deep) 70%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.4s linear infinite;
border-radius: var(--radius);
color: transparent;
user-select: none;
}
@keyframes skeleton-shimmer {
from { background-position: 100% 0; }
to { background-position: -100% 0; }
}
.skeleton-line {
height: 0.9em;
margin: 4px 0;
border-radius: var(--radius-sm);
}
.skeleton-line.short { width: 40%; }
.skeleton-line.medium { width: 70%; }
/* ── Print — optimized for Dafna printing the portrait ─ */
@media print {
:root {
--color-bg: #fff;
--color-surface: #fff;
--color-navy: #000;
--color-ink: #000;
--color-ink-muted: #444;
}
body { background: #fff; color: #000; font-size: 11pt; }
header, .status-bar, .process-panel, .toast, .btn, nav,
#navDiagnostics, .home-sidebar, .home-hero-actions,
#processPanel, #trainingAnalysisCard, #trainingTasksCard {
display: none !important;
}
.main { max-width: 100% !important; padding: 0 !important; }
.page { display: none !important; }
.page.active { display: block !important; }
.portrait-card, .card {
box-shadow: none !important;
border: 1px solid #ccc !important;
page-break-inside: avoid;
margin-bottom: 16px !important;
}
.portrait-headline {
background: #fafafa !important;
border-right: 3px solid #000 !important;
color: #000 !important;
}
h1, h2, h3 { color: #000 !important; page-break-after: avoid; }
.growth-curve, .donut, .hero-timeline { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
.phrase-filters, .btn, button { display: none !important; }
/* Force expand all details */
details { display: block !important; }
summary::marker, summary::-webkit-details-marker { display: none; }
}
/* ── Responsive (desktop-first, minimal mobile) ────── */
@media (max-width: 900px) {
.main { padding: var(--space-5) var(--space-4); }
header { padding: 14px 20px; flex-wrap: wrap; gap: 10px; }
header nav { gap: 2px; }
header nav a { padding: 6px 10px; font-size: 0.82em; }
.home-hero-title { font-size: 2em; }
.style-report-header h1 { font-size: 2em; }
.portrait-card { padding: var(--space-6) var(--space-5); }
.portrait-hero .hero-body { grid-template-columns: 1fr; }
.hero-donut-wrap { justify-content: center; }
.process-panel { width: 280px; }
}

View File

@@ -468,19 +468,55 @@ header nav a.active::after {
/* ── Wizard ────────────────────────────────────────────── */
.wizard-steps {
display: flex; gap: 0; margin-bottom: 24px;
background: #fff; border-radius: 10px; overflow: hidden;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
display: flex;
gap: 0;
margin-bottom: var(--space-6);
background: var(--color-surface);
border-radius: var(--radius-lg);
overflow: hidden;
box-shadow: var(--shadow-sm);
border: 1px solid var(--color-rule-soft);
}
.wizard-step {
flex: 1; padding: 12px 16px; text-align: center; font-size: 0.82em; color: #999;
border-bottom: 3px solid transparent; transition: all 0.2s;
flex: 1;
padding: 16px 18px;
text-align: center;
font-size: 0.88em;
color: var(--color-ink-light);
font-family: var(--font-display);
font-weight: 500;
border-bottom: 3px solid transparent;
transition: all var(--t);
position: relative;
background: var(--color-parchment);
}
.wizard-step:not(:last-child)::after {
content: '';
position: absolute;
left: 0; top: 25%; bottom: 25%;
width: 1px;
background: var(--color-rule);
}
.wizard-step.active {
color: var(--color-navy);
border-bottom-color: var(--color-gold);
font-weight: 700;
background: var(--color-surface);
}
.wizard-step.done {
color: var(--color-success);
border-bottom-color: var(--color-success);
background: var(--color-success-bg);
}
.wizard-step.active { color: var(--color-gold); border-bottom-color: var(--color-gold); font-weight: 600; }
.wizard-step.done { color: var(--color-success); border-bottom-color: var(--color-success); }
.wizard-panel { display: none; }
.wizard-panel.active { display: block; }
.wizard-nav { display: flex; justify-content: space-between; margin-top: 20px; }
.wizard-nav {
display: flex;
justify-content: space-between;
margin-top: var(--space-6);
padding-top: var(--space-5);
border-top: 1px solid var(--color-rule);
}
/* Dynamic list (appellants/respondents) */
.dynamic-list { margin-bottom: 12px; }
@@ -492,34 +528,99 @@ header nav a.active::after {
/* ── Case View ─────────────────────────────────────────── */
.case-header-bar {
background: #fff; border-radius: 10px; padding: 20px; margin-bottom: 16px;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
display: flex; justify-content: space-between; align-items: start; gap: 20px;
background: var(--color-surface);
border-radius: var(--radius-lg);
padding: var(--space-6) var(--space-7);
margin-bottom: var(--space-5);
box-shadow: var(--shadow-sm);
border: 1px solid var(--color-rule-soft);
border-right: 4px solid var(--color-gold);
display: flex;
justify-content: space-between;
align-items: start;
gap: var(--space-6);
position: relative;
}
.case-header-bar .info h2 { font-size: 1.15em; margin-bottom: 6px; }
.case-header-bar .info .meta { font-size: 0.82em; color: #888; display: flex; gap: 16px; flex-wrap: wrap; }
.case-header-bar .links { display: flex; gap: 8px; flex-shrink: 0; }
.case-header-bar::before {
content: '';
position: absolute;
top: 0; left: var(--space-6); right: var(--space-6);
height: 2px;
background: linear-gradient(to left, transparent, var(--color-gold-soft), transparent);
opacity: 0.5;
}
.case-header-bar .info h2 {
font-family: var(--font-display);
font-size: var(--text-2xl);
font-weight: 800;
color: var(--color-navy);
margin-bottom: var(--space-2);
letter-spacing: -0.01em;
}
.case-header-bar .info .meta {
font-size: 0.85em;
color: var(--color-ink-muted);
display: flex;
gap: var(--space-5);
flex-wrap: wrap;
}
.case-header-bar .info .meta strong {
color: var(--color-navy);
font-weight: 600;
}
.case-header-bar .links { display: flex; gap: var(--space-2); flex-shrink: 0; }
.case-header-bar .links a {
font-size: 0.78em; padding: 4px 10px; border-radius: 4px;
text-decoration: none; border: 1px solid #ddd; color: #555; transition: all 0.15s;
font-size: 0.82em;
padding: 8px 14px;
border-radius: var(--radius);
text-decoration: none;
border: 1px solid var(--color-rule);
color: var(--color-ink-muted);
background: var(--color-parchment);
transition: all var(--t);
font-weight: 500;
}
.case-header-bar .links a:hover {
border-color: var(--color-gold);
color: var(--color-gold-deep);
background: var(--color-gold-wash);
}
.case-header-bar .links a:hover { border-color: var(--color-gold); color: var(--color-gold); }
.doc-group { margin-bottom: 16px; }
.doc-group { margin-bottom: var(--space-5); }
.doc-group-header {
font-size: 0.85em; font-weight: 600; color: #555; padding: 8px 0;
border-bottom: 1px solid #eee; margin-bottom: 8px;
display: flex; justify-content: space-between;
font-family: var(--font-display);
font-size: 0.92em;
font-weight: 700;
color: var(--color-navy);
padding: var(--space-3) 0;
border-bottom: 1px solid var(--color-gold);
margin-bottom: var(--space-3);
display: flex;
justify-content: space-between;
letter-spacing: 0.01em;
}
.doc-group-header .count {
font-weight: 400;
color: var(--color-ink-light);
font-family: var(--font-body);
}
.doc-group-header .count { font-weight: 400; color: #999; }
.doc-item {
display: flex; align-items: center; gap: 10px; padding: 8px 12px;
border-radius: 6px; transition: background 0.15s; font-size: 0.85em;
display: flex;
align-items: center;
gap: 12px;
padding: 10px 14px;
border-radius: var(--radius);
transition: all var(--t);
font-size: 0.88em;
border: 1px solid transparent;
}
.doc-item:hover { background: #f8f8f8; }
.doc-item .doc-icon { color: #999; }
.doc-item .doc-name { flex: 1; }
.doc-item .doc-status { font-size: 0.75em; color: #999; }
.doc-item:hover {
background: var(--color-parchment);
border-color: var(--color-rule-soft);
}
.doc-item .doc-icon { color: var(--color-gold); }
.doc-item .doc-name { flex: 1; color: var(--color-ink); font-weight: 500; }
.doc-item .doc-status { font-size: 0.78em; color: var(--color-ink-light); }
.doc-status.completed { color: var(--color-success); font-weight: 700; font-size: 1em; }
.doc-status.processing { display: inline-block; width: 14px; height: 14px; border: 2px solid var(--color-gold); border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; }
.doc-status.pending { color: #ccc; }
@@ -531,12 +632,32 @@ header nav a.active::after {
/* Upload zone (reusable) */
.upload-zone {
border: 2px dashed #ccc; border-radius: 10px; padding: 40px 24px;
text-align: center; cursor: pointer; transition: border-color 0.2s, background 0.2s; background: #fafafa;
border: 2px dashed var(--color-rule);
border-radius: var(--radius-lg);
padding: var(--space-9) var(--space-6);
text-align: center;
cursor: pointer;
transition: all var(--t);
background: var(--color-parchment);
position: relative;
}
.upload-zone:hover,
.upload-zone.dragover {
border-color: var(--color-gold);
background: var(--color-gold-wash);
transform: translateY(-1px);
}
.upload-zone h3 {
font-family: var(--font-display);
font-size: 1.1em;
font-weight: 700;
color: var(--color-navy);
margin-bottom: 6px;
}
.upload-zone p {
font-size: 0.85em;
color: var(--color-ink-muted);
}
.upload-zone:hover, .upload-zone.dragover { border-color: var(--color-gold); background: #fff5f7; }
.upload-zone h3 { font-size: 0.95em; font-weight: 500; color: #555; margin-bottom: 4px; }
.upload-zone p { font-size: 0.78em; color: #aaa; }
.upload-zone input[type="file"] { display: none; }
/* Pending upload items in case view */
@@ -801,25 +922,70 @@ header nav a.active::after {
}
/* ── Skills Management ───────────────────────────────── */
.skill-list { display: flex; flex-direction: column; gap: 10px; }
.skill-list { display: flex; flex-direction: column; gap: var(--space-3); }
.skill-item {
display: flex; align-items: center; gap: 14px; padding: 14px 18px;
border: 1px solid #eee; border-radius: 8px; background: #fafafa;
transition: background 0.15s;
display: flex;
align-items: center;
gap: var(--space-4);
padding: 16px 20px;
border: 1px solid var(--color-rule-soft);
border-radius: var(--radius-lg);
background: var(--color-parchment);
transition: all var(--t);
}
.skill-item:hover { background: #f0f0f0; }
.skill-item .skill-icon { font-size: 1.5em; flex-shrink: 0; }
.skill-item:hover {
background: var(--color-surface);
border-color: var(--color-gold);
box-shadow: var(--shadow-sm);
}
.skill-item .skill-icon { font-size: 1.6em; flex-shrink: 0; color: var(--color-gold); }
.skill-item .skill-info { flex: 1; min-width: 0; }
.skill-item .skill-name { font-size: 0.95em; font-weight: 600; }
.skill-item .skill-meta { font-size: 0.75em; color: #999; margin-top: 3px; display: flex; gap: 14px; flex-wrap: wrap; }
.skill-item .skill-badges { display: flex; gap: 6px; flex-shrink: 0; }
.skill-item .badge-ok { background: #e8f5e9; color: #388e3c; padding: 2px 8px; border-radius: 4px; font-size: 0.72em; font-weight: 600; }
.skill-item .badge-warn { background: #fff3e0; color: #f57c00; padding: 2px 8px; border-radius: 4px; font-size: 0.72em; font-weight: 600; }
.skill-install-result {
margin-top: 16px; padding: 16px; border-radius: 8px;
background: #e8f5e9; border: 1px solid #c8e6c9; font-size: 0.88em;
.skill-item .skill-name {
font-family: var(--font-display);
font-size: 1em;
font-weight: 700;
color: var(--color-navy);
}
.skill-item .skill-meta {
font-size: 0.78em;
color: var(--color-ink-muted);
margin-top: 4px;
display: flex;
gap: var(--space-4);
flex-wrap: wrap;
}
.skill-item .skill-badges { display: flex; gap: 6px; flex-shrink: 0; }
.skill-item .badge-ok {
background: var(--color-success-bg);
color: var(--color-success);
padding: 3px 10px;
border-radius: var(--radius-pill);
font-size: 0.72em;
font-weight: 700;
letter-spacing: 0.02em;
}
.skill-item .badge-warn {
background: var(--color-warn-bg);
color: var(--color-warn);
padding: 3px 10px;
border-radius: var(--radius-pill);
font-size: 0.72em;
font-weight: 700;
letter-spacing: 0.02em;
}
.skill-install-result {
margin-top: var(--space-4);
padding: var(--space-4) var(--space-5);
border-radius: var(--radius-md);
background: var(--color-success-bg);
border: 1px solid var(--color-success);
border-right: 4px solid var(--color-success);
font-size: 0.9em;
}
.skill-install-result.error {
background: var(--color-danger-bg);
border-color: var(--color-danger);
}
.skill-install-result.error { background: #ffebee; border-color: #ffcdd2; }
/* ── Training Corpus Upload ───────────────────────────── */
.training-review {
@@ -1601,9 +1767,19 @@ header nav a.active::after {
<p class="subtitle-muted">דוח ויזואלי על סמך הקורפוס שלמדתי ממך</p>
</div>
<div id="styleReportLoading" class="empty" style="padding:60px 20px">
<div class="mini-spinner" style="width:24px;height:24px"></div>
<div style="margin-top:12px">טוען את הפורטרט...</div>
<div id="styleReportLoading" style="padding: 20px 0">
<div class="portrait-card" style="padding:40px">
<div class="skeleton skeleton-line medium" style="height:2em;margin-bottom:16px"></div>
<div class="skeleton skeleton-line" style="height:3em;margin-bottom:20px"></div>
<div style="display:flex;gap:20px;margin-top:30px">
<div class="skeleton" style="width:160px;height:160px;border-radius:50%"></div>
<div style="flex:1">
<div class="skeleton skeleton-line"></div>
<div class="skeleton skeleton-line"></div>
<div class="skeleton skeleton-line short"></div>
</div>
</div>
</div>
</div>
<div id="styleReportContent" style="display:none">
@@ -1775,11 +1951,23 @@ window.addEventListener('load', () => {
// ── Case List ────────────────────────────────────────────
async function loadCaseList() {
const grid = document.getElementById('caseGrid');
// Show skeleton while loading
grid.innerHTML = `
<div class="case-card"><div class="skeleton skeleton-line medium"></div><div class="skeleton skeleton-line"></div><div class="skeleton skeleton-line short"></div></div>
<div class="case-card"><div class="skeleton skeleton-line medium"></div><div class="skeleton skeleton-line"></div><div class="skeleton skeleton-line short"></div></div>
<div class="case-card"><div class="skeleton skeleton-line medium"></div><div class="skeleton skeleton-line"></div><div class="skeleton skeleton-line short"></div></div>
`;
try {
const res = await fetch(API + '/cases?detail=true');
const cases = await res.json();
if (!cases.length) {
grid.innerHTML = '<div class="empty">אין תיקים עדיין.<br>לחץ "+ תיק חדש" כדי להתחיל.</div>';
grid.innerHTML = `
<div class="empty" style="grid-column:1/-1">
<div style="font-size:2.2em;color:var(--color-gold);margin-bottom:12px;font-family:var(--font-display)">❦</div>
<div style="font-size:1.1em;color:var(--color-navy);margin-bottom:6px;font-family:var(--font-display)">עדיין אין תיקי ערר</div>
<div>לחץ <strong>"+ תיק חדש"</strong> כדי להתחיל</div>
</div>
`;
return;
}
const STATUS_LABELS = {
@@ -1803,7 +1991,7 @@ async function loadCaseList() {
</div>
`).join('');
} catch (e) {
grid.innerHTML = '<div class="empty">שגיאה בטעינת תיקים</div>';
grid.innerHTML = '<div class="empty" style="grid-column:1/-1">שגיאה בטעינת תיקים<br><small style="color:var(--color-ink-light)">' + esc(e.message) + '</small></div>';
}
}
@@ -2356,7 +2544,7 @@ function setupExportUpload(caseNumber) {
const newInput = newZone.querySelector('input[type="file"]');
newZone.addEventListener('click', () => newInput.click());
newZone.addEventListener('dragover', e => { e.preventDefault(); newZone.style.borderColor = 'var(--color-gold)'; });
newZone.addEventListener('dragover', e => { e.preventDefault(); newZone.style.borderColor = '#a97d3a'; });
newZone.addEventListener('dragleave', () => { newZone.style.borderColor = '#ccc'; });
newZone.addEventListener('drop', e => {
e.preventDefault();
@@ -2765,7 +2953,7 @@ async function loadSkillList() {
actions.push(`<button class="btn btn-sm btn-secondary" onclick="syncSkill('${esc(s.slug)}')">Re-sync</button>`);
}
if (inDb) {
actions.push(`<button class="btn btn-sm btn-outline" style="color:var(--color-gold);border-color:var(--color-gold)" onclick="deleteSkill('${esc(s.slug)}')">Delete from DB</button>`);
actions.push(`<button class="btn btn-sm btn-outline" style="color:#a97d3a;border-color:#a97d3a" onclick="deleteSkill('${esc(s.slug)}')">Delete from DB</button>`);
}
return `
<div class="skill-item">
@@ -2899,7 +3087,7 @@ async function restartPaperclip() {
const data = await res.json();
if (!res.ok) {
status.textContent = 'Error: ' + (data.detail || 'failed');
status.style.color = 'var(--color-gold)';
status.style.color = '#a97d3a';
toast('Restart failed', 'error');
} else {
status.textContent = 'Restarted successfully';
@@ -2908,7 +3096,7 @@ async function restartPaperclip() {
}
} catch (e) {
status.textContent = 'Network error';
status.style.color = 'var(--color-gold)';
status.style.color = '#a97d3a';
toast('Network error', 'error');
} finally {
btn.disabled = false;
@@ -3279,7 +3467,9 @@ const SECTION_COLORS = {
conclusion: '#b8894a',
};
const DONUT_COLORS = ['var(--color-gold)', '#5e9a6e', '#4e7cb3', '#a7547c', '#c87533', '#7e5c9a', '#b8894a'];
// Hex literals (not CSS vars) — used both in CSS strings (conic-gradient)
// and in inline dot backgrounds. Tuned to the editorial palette.
const DONUT_COLORS = ['#a97d3a', '#5e9a6e', '#4e7cb3', '#a7547c', '#c87533', '#7e5c9a', '#b8894a'];
let _styleReportData = null;
let _activeFilter = 'all';