feat(legal): B-2.9 6 pages légales (CGU, Loi 25, cookies, remboursement, accessibilité, mentions)

- src/legal/__init__.py: define canonical LEGAL_VERSION='2026-04-27' constant
  (single source of truth — auth.py now imports it as SIGNUP_LEGAL_VERSION).
- src/legal/routes.py: add /legal/<page> + /legal/ index routes; markdown rendered
  from src/legal/content/*.md with toc, tables, fenced_code, attr_list extensions.
- src/legal/content/: 6 French (Québec) markdown documents — DictIA Inc. /
  InnovA AI S.E.N.C. branding, Loi 25-compliant 12-section privacy policy,
  WCAG 2.2 AA accessibility statement, AGPL-3.0 attribution. All marked
  DRAFT v1.0 pending legal review by Allison Rioux.
- templates/legal/_layout.html + index.html: extends marketing/base.html;
  inline .legal-content typographic styles (no CSS rebuild required).
- .gitignore: allow-rule for src/legal/content/*.md so markdown is tracked
  despite the global *.md ignore.
- tests/test_legal_pages.py: 9 tests covering 200 responses, DictIA branding,
  rprp@dictia.ca presence, 12 mandatory Loi 25 sections, public indexability
  (no X-Robots-Tag noindex), shared layout, marketing/base.html extension,
  DRAFT callout, and LEGAL_VERSION/SIGNUP_LEGAL_VERSION equivalence.
- tests/_run_legal_pages_windows.py: manual driver (Windows fcntl stub).
- static/css/marketing.css: regenerated by `npm run build:css` to include
  new utility classes referenced from templates/legal/*.html.

Tests: 9/9 pass. No off-limits files modified beyond the 2-line auth.py
constant move spec'd in B-2.9. No schema changes; markdown==3.5.1 already
pinned in requirements.txt (B-1.1). Pages publicly indexable by design
(Loi 25 transparency).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Allison
2026-04-28 08:57:36 -04:00
parent 64738bfd1f
commit 55569366f4
15 changed files with 1034 additions and 6 deletions

View File

@@ -0,0 +1,134 @@
{% extends 'marketing/base.html' %}
{% block title %}{{ title }} — DictIA{% endblock %}
{% block description %}{{ description }}{% endblock %}
{% block head_extra %}
<style>
/* Typographic styles for markdown-rendered legal content.
Inlined here so we don't need to rebuild static/css/marketing.css. */
.legal-content h2 {
font-size: 1.5rem; /* 24px */
line-height: 2rem;
font-weight: 700;
color: #060d1a; /* brand-navy */
margin-top: 2rem;
margin-bottom: 0.75rem;
letter-spacing: -0.022em;
}
.legal-content h3 {
font-size: 1.25rem; /* 20px */
line-height: 1.75rem;
font-weight: 600;
color: #060d1a;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
}
.legal-content h4 {
font-size: 1.05rem;
font-weight: 600;
color: #060d1a;
margin-top: 1.25rem;
margin-bottom: 0.5rem;
}
.legal-content p {
margin-bottom: 1rem;
font-size: 1rem;
line-height: 1.7;
}
.legal-content ul,
.legal-content ol {
margin-bottom: 1rem;
margin-left: 1.5rem;
line-height: 1.7;
}
.legal-content ul { list-style-type: disc; list-style-position: outside; }
.legal-content ol { list-style-type: decimal; list-style-position: outside; }
.legal-content li { margin-bottom: 0.25rem; }
.legal-content a {
background: linear-gradient(118deg, #0062ff, #00bdd8 52%, #00c896);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
font-weight: 600;
text-decoration: underline;
text-decoration-color: #0062ff;
}
.legal-content a:focus-visible {
outline: 2px solid #0062ff;
outline-offset: 2px;
border-radius: 2px;
}
.legal-content table {
width: 100%;
margin-bottom: 1rem;
border-collapse: collapse;
font-size: 0.875rem;
}
.legal-content th,
.legal-content td {
border: 1px solid #e6ebf2;
padding: 0.5rem;
text-align: left;
}
.legal-content th {
background-color: #f7f9fc;
font-weight: 600;
}
.legal-content blockquote {
border-left: 4px solid #0062ff;
padding-left: 1rem;
margin: 1rem 0;
font-style: italic;
color: rgba(6, 13, 26, 0.7);
}
.legal-content code {
padding: 0.15rem 0.4rem;
background-color: #f7f9fc;
border-radius: 4px;
font-size: 0.875rem;
font-family: 'JetBrains Mono Variable', 'JetBrains Mono', monospace;
}
.legal-content hr {
margin: 2rem 0;
border: none;
border-top: 1px solid #e6ebf2;
}
.legal-content strong { font-weight: 700; color: #060d1a; }
/* DRAFT callout — visually distinct yellow banner */
.legal-content .draft-callout,
.legal-draft-callout {
background-color: #fffbeb;
border-left: 4px solid #f59e0b;
padding: 0.75rem 1rem;
margin: 1rem 0 1.5rem;
border-radius: 0 8px 8px 0;
font-size: 0.9rem;
color: #78350f;
}
</style>
{% endblock %}
{% block content %}
<section class="bg-brand-bg py-16 px-4" aria-labelledby="legal-title">
<article class="max-w-3xl mx-auto bg-white p-8 md:p-10 rounded-[18px] border border-brand-border shadow-cta">
<header class="mb-8 pb-6 border-b border-brand-border">
<p class="text-xs uppercase tracking-wider text-brand-navy/70 mb-2">Document légal DictIA</p>
<h1 id="legal-title" class="text-3xl md:text-4xl font-black text-brand-navy mb-3">{{ title }}</h1>
<p class="text-sm text-brand-navy/70">
Version {{ legal_version }}{{ ' ' }}&middot;{{ ' ' }}Dernière révision&nbsp;: {{ legal_version }}.
Pour toute question, écrivez à <a href="mailto:rprp@dictia.ca" class="grad-text font-semibold">rprp@dictia.ca</a>.
</p>
</header>
<div class="legal-content text-brand-navy/90 leading-relaxed">
{{ content | safe }}
</div>
<footer class="mt-10 pt-6 border-t border-brand-border text-sm text-brand-navy/70">
<p>&larr; <a href="{{ url_for('legal.legal_index') }}" class="grad-text font-semibold">Index des documents légaux</a></p>
</footer>
</article>
</section>
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% extends 'marketing/base.html' %}
{% block title %}{{ title }}{% endblock %}
{% block description %}{{ description }}{% endblock %}
{% block content %}
<section class="bg-brand-bg py-16 px-4 min-h-[60vh]" aria-labelledby="legal-index-title">
<div class="max-w-3xl mx-auto">
<header class="mb-8 text-center">
<p class="text-xs uppercase tracking-wider text-brand-navy/70 mb-2">DictIA Inc.</p>
<h1 id="legal-index-title" class="text-3xl md:text-4xl font-black text-brand-navy mb-3">Documents légaux</h1>
<p class="text-sm text-brand-navy/70">Version {{ legal_version }} de l'ensemble des documents.</p>
</header>
<ul class="space-y-3" role="list">
{% for page in pages %}
<li>
<a href="{{ url_for('legal.legal_page', page=page.slug) }}" class="block p-5 bg-white border border-brand-border rounded-[12px] hover:border-brand-b1 hover:shadow-cta transition focus-visible:outline-2 focus-visible:outline-brand-b1 focus-visible:outline-offset-2">
<h2 class="text-lg font-semibold text-brand-navy mb-1">{{ page.title }}</h2>
<p class="text-sm text-brand-navy/70">{{ page.description }}</p>
</a>
</li>
{% endfor %}
</ul>
</div>
</section>
{% endblock %}