Commit Graph

33 Commits

Author SHA1 Message Date
Allison
e49652d85d feat(marketing): intègre pipeline 4 étapes + réseau hub d'intégrations depuis dictia.ca/solutions/dictai
- Pipeline (entre Solution & Bento) : Upload → GPU WhisperX → IA Mistral → Export
  Auto-advance Alpine 2400ms, sweep ring SVG CSS, dot glow, prefers-reduced-motion
- Hub (entre Bento & Pricing) : DictIA → 3 hubs → 9 outils
  SVG natif <animateMotion> sur bezier paths, zéro lib JS, fallback liste 3-col WCAG
- Texte 100% canonique extrait de Website-Sanity dictai-pipeline.tsx + dictai-hub.tsx
- OQLF NBSP : "1 heure d'audio → 2 minutes", "5 000+ apps", "100 % en local"
- WCAG : aria-labelledby sections, role=tab/list, focus-visible, prefers-reduced-motion
- +397 lignes, npm run build:css exécuté pour utilities cyan/purple/opacity

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 12:03:33 -04:00
Allison
aad37f8566 feat(marketing): hero 2-col avec mockup app DictIA animé interactif
Refonte de la section hero: passage d'un layout centré single-col
à un grid 2 colonnes (texte gauche + mockup app à droite) sur lg+,
avec préservation du centrage actuel sur mobile/tablette.

- Mockup ~560×500px reproduit l'interface DictIA réelle:
  - Window chrome (3 dots traffic light + tab "DictIA — Enquêter")
  - Sidebar: 6 enregistrements groupés (Semaine dernière 2 + Mois
    dernier 4) avec chips colorés (En cours, Barreau Confidentiel,
    CPA Corporatif, Urgent Client) + bouton + recording rounded-none
  - Center: header card avec 4 metas (avatars/calendar/clock/file),
    audio player avec progress bar animée 50%-75% (15s loop),
    transcript 5 lignes speaker (Allison/SPEAKER_02) où la ligne
    active cycle toutes les 2.8s via Alpine x-data idx
  - Right: tabs Résumé/Notes/Discuter (Résumé actif), résumé
    exemple + 4 points clés
- Tilt subtil rotate-1 → straighten + scale au hover (lg only)
- 2 glow orbs flottants décoratifs derrière (bg-brand-b1/15 +
  bg-brand-b3/10) avec tc-float-y reverse
- role="img" + aria-label descriptif sur le mockup complet
- prefers-reduced-motion désactive toutes animations + freeze
  progress bar à 60% + retire transform tilt
- Tous les éléments interactifs ont tabindex="-1" + aria-hidden
  car purement décoratifs (pas de duplication d'app réelle)
- Aucun emoji (SVG inline stroke="currentColor" partout)
- Système border-radius respecté: rounded-none (boutons/inputs/
  tuiles), rounded (4px wrapper card), rounded-full (chips/avatar)

Tests: 6/6 hero tests pass (eyebrow, h1+grad-text, dual CTA,
cosmic orbs, social proof, animations staggered). Les 3 fails
restants (test_landing_has_main_nav, test_footer_links_complete,
test_trust_bar_has_eyebrow_factual_phrasing) sont préexistants
et sans rapport avec la refonte hero.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 11:45:35 -04:00
Allison
3e56736fa7 feat(marketing): refonte fonctionnalites avec contenu canonique + animations modernes subtiles
7 sections (hero stats counter, sticky sub-nav, 6 fonctionnalités bento avec chips
specs, 3 sous-groupes intégrations, tableau architecture 3 tiers, conformité résumée
+ lien /conformite, CTA final). Contenu canonique extrait du site prod (WhisperX
Large-v3, pyannote-audio, Mistral 7B, RAG sentence-transformers, 8 locuteurs,
30× temps réel, 95%+ FR-CA, prix 3 450/5 750/369 $).

Animations: counter rAF easeOutCubic via Alpine + IntersectionObserver, fade-in
stagger via data-ani-fade, animated underline H2, hover lift cards, sticky sub-nav
avec active highlight, cosmic orbs flottantes, pulse glow sur card recommandée.
Toutes les animations respectent prefers-reduced-motion via media query inline.

Conserve les sections exports/specs/integrations grid pour rétro-compat tests.
13 assertions pytest fonctionnalites passent (les 2 failures conformite sont
pré-existantes sur Windows — mojibake console, non liées à cette refonte).
2026-04-28 11:28:15 -04:00
Allison
55569366f4 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>
2026-04-28 08:57:36 -04:00
Allison
b8fa321edd feat(auth): B-2.6 WebAuthn / Passkey support (FIDO2 + biometric 2FA)
Adds phishing-resistant 2nd factor via FIDO2 hardware keys (YubiKey etc.)
and device biometrics (Touch ID, Windows Hello, etc.). Reuses the existing
B-2.5 TOTP gate so a passkey is a 3rd valid option on /2fa/verify, alongside
TOTP code and recovery code. Post-login enrolment lives at /2fa/passkey/setup.

Wraps python-webauthn==2.5.2 in a thin service layer (src/auth/webauthn.py)
that persists credentials in the existing User.webauthn_credentials JSON
column (added in B-2.1 — no schema change). Each credential dict carries
id, public_key, sign_count, transports, name, and created_at. sign_count is
updated after every successful authentication for WebAuthn anti-cloning
(§6.1.1).

Backend: 6 new auth routes (passkey_setup, register/begin, register/finish,
delete, auth/begin, auth/finish). The 4 JSON endpoints are CSRF-exempt at
Flask-WTF level because CSRFProtect cannot read tokens from a JSON body
without app-wide config; the X-CSRFToken header is still sent as
defence-in-depth. The form-POST delete route DOES enforce CSRF. The
@csrf_exempt decorator was previously a no-op label; init_auth_extensions
now walks module-level functions and applies real csrf.exempt() to any
flagged with _csrf_exempt=True.

Login gate now fires when the user has TOTP enabled OR at least one
passkey, and totp_verify_login passes has_passkeys + has_totp flags so the
template can show only the relevant sections.

Frontend: templates/auth/totp_verify.html updated IN PLACE with a passkey
button section (above TOTP) and an "ou" divider. New
templates/auth/passkey_setup.html for managing/enrolling passkeys. New
static/js/webauthn-client.js (no external deps, ES2020) wraps
navigator.credentials and exchanges base64url payloads with the backend.
Tailwind CSS rebuilt.

Tests: 22 new tests in tests/test_webauthn_passkey.py covering the service
layer (b64url helpers, RP config, list/has, begin/finish for both
registration and authentication, delete) and the route flow (CSRF-exempt
JSON endpoints, login gate redirection, sign_count anti-cloning
persistence). Mocks python-webauthn's verify_* functions so tests run
without a real authenticator. Windows manual driver follows the existing
no-conftest pattern.

Self-review: 22/22 new tests pass; 21/21 prior TOTP, 16/16 email,
21/21 OAuth tests still pass (no regression).

Env: config/env.oauth.example documents WEBAUTHN_RP_ID, WEBAUTHN_RP_NAME,
WEBAUTHN_ORIGIN with full deployment notes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:27:09 -04:00
Allison
aa269c5bc0 feat(auth): B-2.5 TOTP MFA + recovery codes (Fernet-encrypted secret)
Adds TOTP-based two-factor authentication (RFC 6238) with 10 single-use
recovery codes. Secret is encrypted at rest with a Fernet key derived
deterministically from app SECRET_KEY (SHA-256 -> urlsafe-base64); the raw
base32 secret never lives in the database. Recovery codes are bcrypt-hashed
and consumed atomically (single-use, removed from the JSON list on match).

Routes:
- GET /2fa/setup: generate fresh secret + QR + 10 recovery codes; cache
  pending state in session, render auth/totp_setup.html with inline QR
  data URL and the 10 codes shown ONCE.
- POST /2fa/setup: verify the user-submitted 6-digit code against the
  pending secret; on success persist encrypted secret + hashes and flip
  totp_enabled=True. On invalid code re-render same QR (don't rotate),
  preserving the user's authenticator scan.
- GET /2fa/verify: second factor during login; reads pending_totp_user_id
  from session and renders auth/totp_verify.html (TOTP code input +
  collapsed recovery code form, with X codes restants notice).
- POST /2fa/verify: accepts EITHER a 6-digit TOTP code OR a recovery code;
  on success finalises login_user (preserving remember-me intent + next
  URL captured at the password step), audits success/failure.
- POST /2fa/disable: requires password re-auth; nullifies the 3 TOTP fields.

Login gate (src/api/auth.py /login): after password+email-verification
checks but BEFORE login_user, if user.totp_enabled set
session['pending_totp_user_id'] / pending_totp_remember /
pending_totp_next and 302 -> /2fa/verify. OAuth/SSO/magic-link paths are
intentionally NOT gated in B-2.5 (deferred — IdP handles its own MFA).

Schema:
- New JSON column User.totp_recovery_codes (nullable) added via
  add_column_if_not_exists in src/init_db.py (no Alembic, follows existing
  pattern).
- Re-uses B-2.1 columns totp_secret_encrypted (VARCHAR 255) and
  totp_enabled (BOOLEAN); both already migrated.

Compatibility audit overrides honoured:
- Service layer at src/auth/totp.py (NOT a new src/auth_extended/ pkg).
- Templates at templates/auth/totp_setup.html and templates/auth/totp_verify.html
  extending marketing/base.html with brand tokens + WCAG patterns
  (focus-visible, role=alert, aria-required, autocomplete=one-time-code,
  inputmode=numeric).
- account.html integration deferred to a polish task — admins access
  /2fa/setup directly for now.

Tests (21, all green via Windows manual driver):
- Service layer: encrypt/decrypt round-trip, key-mismatch rejection, secret
  validity, code verification (current/wrong/non-digit), recovery codes
  (10 pairs, 1:1 bcrypt mapping, single-use consumption, unknown rejection),
  set/disable user TOTP fields.
- Routes: login redirect-to-/2fa/verify when totp_enabled, direct login
  when disabled, /2fa/verify with correct/wrong TOTP, recovery code consume,
  redirect-to-login when no pending session, /2fa/setup GET creates pending,
  POST with valid code enables MFA, POST with invalid code keeps pending +
  returns 400, /2fa/disable wrong/correct password.

Regression check: prior 21 OAuth+magic-link, 16 email-service, and 9
signup-Loi-25 tests all still pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 00:08:40 -04:00
Allison
0513e67838 feat(auth): B-2.4 OAuth Microsoft/Google + magic link (Loi 25 deferred consent)
Adds Microsoft 365 + Google OAuth providers (separate from the existing
generic OIDC SSO at src/auth/sso.py) and a passwordless magic-link login
flow. New OAuth signups capture Loi 25 art. 14 consents (4 granular
checkboxes) BEFORE creating the User row via /auth/oauth/finish-signup.

Per compatibility-audit.md C2:
- No src/auth_extended/ directory — extends src/auth/ in place
- No new User columns — reuses sso_provider/sso_subject + email_verified
- Magic-link tokens via itsdangerous URLSafeTimedSerializer (15-min, no DB)
- All routes added to existing auth_bp; templates extend marketing/base.html
- Anti-enumeration on /auth/magic-link (generic flash for unknown OR
  unverified emails) and /auth/magic-link/<token> (same flash for
  invalid/expired/unverified-user)

Files added:
- src/auth/oauth_providers.py — Microsoft + Google OAuth registration,
  is_oauth_provider_enabled(), find_user_by_oauth(), create_oauth_user_with_consent()
- src/auth/magic_link.py — generate/consume magic-link tokens
- templates/auth/magic_link_request.html, templates/auth/oauth_finish_signup.html
- tests/test_oauth_magic_link.py + tests/_run_oauth_magic_link_windows.py (16 tests)
- config/env.oauth.example

Files modified:
- src/api/auth.py — 5 new routes (oauth_provider_login/callback,
  oauth_finish_signup, magic_link_request/consume); login flashes translated FR;
  oauth_*_enabled flags passed to login template
- src/app.py — wires init_oauth_providers(app) after blueprint registration
- src/services/email.py — adds send_magic_link_email() (FR + DictIA brand)
- templates/login.html — refondu IN PLACE (was 178 lines legacy Vue/TW3)
  to extend marketing/base.html with OAuth buttons, password form,
  magic-link CTA, signup link
- templates/auth/check_email.html — adds action='magic_link' branch
- static/css/tailwind.config.js — adds templates/login.html to content
- static/css/marketing.css — rebuilt

Tests: 16/16 PASS via Windows manual driver.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:29:24 -04:00
Allison
37639a7d09 feat(auth): B-2.3 emails FR + DictIA branding (SMTP Resend)
Rebrand src/services/email.py IN PLACE: French + DictIA + brand gradient
(#0062ff/#00bdd8/#00c896) — replaces legacy "Speakr" / #2563eb. Greetings now
use user.name with fallback to user.username. Subjects:
"Vérifiez votre courriel — DictIA" + "Réinitialiser votre mot de passe — DictIA".
SMTP_FROM_NAME defaults to DictIA. Footer points to info@dictia.ca with the
Loi 25 tagline.

Refonte 4 auth templates IN PLACE pour étendre marketing/base.html : check_email,
forgot_password, reset_password, verify_success. Tokens DictIA (brand-navy,
brand-bg, grad-bg, shadow-cta), French copy, WCAG patterns (label for,
focus-visible:outline-2, role=alert, aria-required, text-brand-navy/70 minimum,
NBSP français pour Loi 25 / 24 heures / 1 heure / 8 caractères).

Translate inline French flash messages in src/api/auth.py for /verify-email,
/resend-verification, /forgot-password, /reset-password. Anti-enumeration fix:
forgot_password no longer flashes the cooldown remaining (would leak account
existence) — silently skips resend, generic flash unchanged. Cooldown logic
in src/services/email.py UNCHANGED (60s — verified by test).

config/env.email.example: defaults to Resend SMTP at the top + adds Resend
to the provider examples list (preserves Gmail/SendGrid/Mailgun/SES/M365).

Tests: tests/test_email_service_dictia.py — 12 tests covering DictIA branding,
French copy, display-name fallback, anti-enumeration parity (forgot_password
returns identical message for known/unknown emails), 60s cooldown, SMTP-not-
configured returns False (no exception), check_email.html extends marketing/base
(no var(--text-primary) leaks). Includes Windows manual driver
(_run_email_service_dictia_windows.py) since pytest cannot collect on Windows
native (fcntl POSIX-only).

NO new dependency added (no resend SDK — SMTP via existing _send_email).
NO new route added or removed.
NO src/auth_extended/ created.
NO change to itsdangerous-based token logic.
templates/auth/**/*.html already in tailwind.config.js content array (B-2.2).

Verified locally on Windows manual driver: 12/12 PASS B-2.3, 9/9 PASS regression
on B-2.2 signup, 9/9 PASS regression on B-2.1 ConsentLog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 23:02:20 -04:00
Allison
3b324ad0b9 fix(auth): B-2.2 review fixes — Tailwind path + WCAG + race + flash + tests
C-1: Add templates/register.html (and templates/auth/**) to tailwind.config.js
content array so utility classes used by the signup template don't get purged
on next build. Rebuilt static/css/marketing.css; verified text-brand-navy/90
and min-h-[calc(100vh-62px)] are now compiled.

I-1: Replace flash() calls for missing required consents with WTForms
field-level errors (form.consent_cgu.errors.append / form.consent_confidentialite
.errors.append). Errors render inline next to each consent checkbox via
{% if form.consent_cgu.errors %}<p role="alert">…</p>{% endif %}. Prevents
session-backed flash messages from leaking across unrelated navigations.

I-2: Wrap user creation + flush in IntegrityError retry loop (max 5 attempts);
import IntegrityError from sqlalchemy.exc. Absorbs the inherent race between
_generate_unique_username's lookup and the subsequent flush under concurrent
signups. Added docstring note to _generate_unique_username explaining the
wrapper.

I-3: Move db.create_all() inside the try/finally in
test_signup_route_csrf_enforced so WTF_CSRF_ENABLED is restored even if
table creation fails.

I-4: Pin test_signup_rejects_duplicate_email assertion to status_code == 200
(WTForms validate_email raises ValidationError → form fails validation →
fall-through to default 200 render_template).

I-5: Add id="password-help" to the password help paragraph and
aria-describedby="password-help" to the password input so screen readers
announce the password requirements when the field is focused.

I-6: Bump flash banner text colors from -700/-800 to -900 variants
(text-amber-900, text-blue-900, text-red-900, text-green-900) for safer
WCAG 2.2 AA contrast against the -50 backgrounds. Same bump applied to the
new consent and password inline error renders.
2026-04-27 22:43:00 -04:00
Allison
3646a5e64d feat(marketing): A-2.8b /conformite + /contact standalone pages
- /conformite page: extends base.html, page H1 with cosmic orb header,
  4 pillar cards on white (mirrors landing's Conformite section content
  with same hedges 'Mappe' 'concue avec' 'Compatible'), 3 Loi 25 article
  detail cards (art. 3.3 EFVP, art. 3.5 Audit trail, art. 14 Consentement)
  with grad-bg article-number badges, AGPL v3 transparency CTA section
  with external links to Gitea + gnu.org (rel=noopener), generic CTA section
- /contact page: extends base.html, 3 method cards (email, phone tel:link,
  postal address with <address>), 6 pre-filled mailto subject shortcuts
  with focus-visible WCAG 2.2 AA, pre-launch disclaimer that online form
  ships at launch (B-2.x). NO <form> tag - mailto only - POST returns 405
  until B-2.x adds the form handler.
- routes.py: add /conformite and /contact routes; preserves existing
  landing/tarifs/fonctionnalites views and TESTIMONIALS/FAQ data.
- tests: append 13 new tests to test_marketing_secondary_pages.py covering
  routes 200, single H1, 4 pillars + Loi 25 articles + AGPL externals on
  /conformite, 3 contact methods + 6 shortcuts + 405 on POST + pre-launch
  note + OQLF typography on /contact.
- Apply established WCAG 2.2 AA, FlexiHub, OQLF, LPC art. 219 disciplines.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 21:23:47 -04:00
Allison
202e1a08d9 fix(marketing): A-2.8a — extract pricing partial + sync bento + OQLF + test calibration
- Extract 3 pricing tiers to templates/marketing/_partials/_pricing_tiers.html
  Single source of truth — landing.html and tarifs.html now {% include %} it.
  Prevents price drift (LPC art. 219 risk).
- Sync bento card #2 description across landing + fonctionnalites
  (was diverged: 'embeddings' vs 'embeddings vocaux'). Add maintenance
  reminder comments in both files.
- Fix OQLF NBSP on '~2 semaines' matrix cells in /tarifs deep-dive table.
- Fix mixed UTF-8/entity 'qu&eacute;b&eacute;cois' -> 'québécois' in tech
  specs (consistent with rest of file).
- Calibrate H2 size on /tarifs FAQ to match landing (clamp 2.75rem cap).
- Repair 2 pre-existing test bugs from earlier A-2.x commits:
  * 'violent la Loi 25' -> accept both NBSP and plain forms (commit 7c6c6fd
    added the NBSP after the test was written)
  * 'r&eacute;silie' -> 'résilie' (Jinja outputs raw UTF-8, not entities)
- Update src/marketing/routes.py module docstring to reflect 2/4 done.
2026-04-27 21:06:26 -04:00
Allison
d471626183 feat(marketing): A-2.8a /tarifs + /fonctionnalites standalone pages
- /tarifs page: extends base.html, reuses pricing_card + button macros,
  shows the 3 forfaits with NBSP-formatted CAD prices, an 8-row deep-dive
  comparison matrix (DictIA 8 vs DictIA 16 vs DictIA Cloud), 5 tarification
  FAQ items (frais cachés, migration, GPU, taxes TPS/TVQ, plans annuels)
  with Alpine accordion + focus-visible WCAG 2.2 AA, CTA section
- /fonctionnalites page: extends base.html, reuses bento_card macro,
  re-renders the 6 features with same content as landing's bento section
  for consistency, adds dedicated 7-format export grid + 8-integration
  grid (with trademark disclaimer) + 6 tech specs section (Whisper/pyannote
  /Mistral/stack/audio/langues), CTA section
- routes.py: add /tarifs and /fonctionnalites routes (passes FAQ to /tarifs
  for the tarification accordion; preserves existing landing(), TESTIMONIALS,
  FAQ data structures unchanged)
- tests/test_marketing_secondary_pages.py: NEW test file (16 tests covering
  routes 200, base.html inheritance, H1 anchors, 3 pricing cards, comparison
  matrix, tarifs FAQ accordion, OQLF typography, 6 bento + 7 exports + 8
  integrations + 6 tech specs sections, canonical meta)
- All sections respect WCAG 2.2 AA, FlexiHub design discipline, LPC art. 219
  hygiene (sourcing dates, trademark disclaimer, hedged claims, NBSP)
2026-04-27 20:50:07 -04:00
Allison
2b3eeb98e0 fix(marketing): A-2.7b WCAG 2.2 AA polish + JSON-LD test hardening
- Drop role="region" from FAQ panels (had no accessible name — axe-core
  violation; disclosure pattern with button + aria-controls + aria-expanded
  is sufficient per WAI-APG accordion guidance)
- Add focus-visible:outline-2 outline-brand-b1 outline-offset-2 to FAQ
  buttons (WCAG 2.2 AA 2.4.7 Focus Not Obscured + 2.4.11 Focus Appearance —
  Safari default focus indicator is unreliable)
- Sweep pre-existing text-white/50 on Hero social proof microcopy → /70
  (branch-wide WCAG floor; recurring landmine flagged at A-2.7a review)
- Strengthen test_faq_jsonld_schema_present to json.loads() the extracted
  block and validate the FAQPage schema shape (regression guard for future
  content edits with unescaped backslashes/quotes)
2026-04-27 20:34:53 -04:00
Allison
824ea638de feat(marketing): A-2.7b témoignages placeholder + FAQ accordion + CTA + JSON-LD
- Pre-launch testimonials section: 3 placeholder cards (avocat, CPA, municipal)
  with persona icons + 'Témoignage à venir' label — NO fabricated quotes
  (LPC art. 219). Expected publication mai-juin 2026 from T-4.1 interviews.
- FAQ accordion: 7 verifiable Q&A using Alpine.js core (x-data + x-show +
  built-in x-transition; NO x-collapse plugin). Each item has @click toggle,
  :aria-expanded, aria-controls, role="region" panel, focusable button.
- Schema.org FAQPage JSON-LD inline at end of FAQ section — striptags +
  replace('&nbsp;', ' ') to normalize entities for Google FAQ rich result.
- CTA final: 'Réservez votre pré-inscription' (mailto + #tarifs anchor),
  cosmic orbs to mirror Hero (page closure), ghost variant secondary button.
- Inline TESTIMONIALS and FAQ Python lists in src/marketing/routes.py
  (no PyYAML dep — YAGNI; T-4.1 can introduce it when real data warrants).
- 8 new tests covering testimonials placeholders, forbidden fake names,
  7 FAQ panels, Alpine bindings, JSON-LD schema, CTA wording, route data.
2026-04-27 19:52:36 -04:00
Allison
0d69fcd034 feat(marketing): A-2.7a footer 4-col + comparatif table + conformité 4 pillars
- Replace placeholder _footer.html with full 4-column footer (Brand, Produit,
  Légal, Compte) — added aria-labelledby landmark, sr-only headings, address
  block, tel: link, external-link new-tab announcements, NBSP per OQLF
- Add Comparatif section: DictIA vs MS Teams vs Otter.ai vs Whisper local
  on 6 criteria. Sourced (politiques publiques + grilles officielles 2026-04-27),
  trademark disclaimer, responsive table with sr-only caption + scope attrs
- Add Conformité forteresse section: 4 pillars (OVH Beauharnois, mapped Loi 25
  LPRPSP refs, compatible Cadre IA LGGRI, AGPL v3 verifiable Gitea link)
- Soft hedges throughout: 'mappé', 'conçu avec', 'compatible' (no 'certifié',
  'endossé', 'reconnu' — LPC art. 219 / Competition Act s. 52 hygiene)
- 11 new tests covering footer landmarks, comparatif sourcing, conformité
  hedges, WCAG /70 contrast, and forbidden competitive claim regressions

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:21:39 -04:00
Allison
7d67b64ddc fix(marketing): pricing — honest ROI payback + capped sliders + URL hygiene
- ROI payback now returns raw months; template branches to 'moins d'un mois'
  for sub-month paybacks and 'Payable dès la première année' when savings≤0
  (was rounding up to 'Payback : 1 mois' for ~95% of slider combos)
- Cap sliders: users 1..25 (was 50), hours 0.5..4 (was 8) to keep displayed
  savings in a defensible band (~8.8 M$/yr max instead of 35 M$)
- pricing_card href uses cta_url.rstrip('/') to avoid double-slash if caller
  passes a trailing slash (preempts A-2.8 / B-2.7 regression)
- aria-live polite + aria-atomic on the savings paragraph so screen readers
  announce slider updates
- Cleaner JS module pattern: single window.roiCalculator = function() {...}
- Tests updated for payback ternary; new tests for slider caps, aria-live,
  and double-slash guard

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:05:36 -04:00
Allison
0ae4053faa feat(marketing): pricing 3 forfaits + ROI calculator Alpine.js 2026-04-27 18:50:33 -04:00
Allison
b87f35ea4a fix(marketing): bento autoescape + dead col-span + test gaps
- Pipe macro title/description through | safe to render NBSP/&amp; correctly
  (autoescape was producing literal '95&nbsp;%+' and 'Q&amp;R' text on screen)
- Replace dynamic col-span-{{ span }} with static lookup table so Tailwind
  scanner generates the utilities for A-2.7+ reuse
- Replace inline border style with border-white/[0.045] utility (codebase consistency)
- Add explicit Q&R assertion + autoescape regression guard test
2026-04-27 18:19:56 -04:00
Allison
775075d1ea feat(marketing): bento grid 6 features (style FlexiHub) 2026-04-27 18:03:57 -04:00
Allison
3c471a72d1 feat(marketing): PAS frame sections (Problème + Solution) after trust bar
- Problème section (light bg-brand-bg, white cards): 3 risk cards
  (Cloud Act, Loi 25 biométrie art. 60.1 LPRPSP, sanctions disciplinaires
  9 ordres pros). H2 grad-text accent on 'violent la Loi 25' — defensible
  legal claim citing CAI + LPRPSP statutes by name. text-brand-navy/70 for
  all body text (WCAG AA compliant, no /40 or /50 regression).
- Solution section (bg-brand-navy with single subtle green orb): 3 pillars
  (100% local, Conforme Loi 25, Précision FR-CA). H2 grad-text accent on
  'par design'. Pillars cite specific tech (WhisperX Large-v3, Mistral 7B,
  pyannote, OVH Beauharnois, AGPL v3) — all factually verifiable.
- French typography: NBSP via &nbsp; + |safe before %, $, and within
  'Loi 25' to prevent line break separation (OQLF rule).
- 4 new tests verify both sections, 3 cards each, key tech mentions,
  and WCAG-safe opacity policy.
2026-04-27 17:42:46 -04:00
Allison
54168e443b fix(marketing): trust bar accuracy + WCAG AA contrast + LPC art. 219 hygiene
- Critical (C1): align 9-ordre list with dictia.ca canonical (Barreau, CNQ,
  CPA, ChAD, OACIQ, CMQ, OIIQ, OPQ, OEQ). Drop ambiguous OPPQ; replace M/P
  short monograms with disambiguated 3-5 char abbreviations (BAR, CNQ, CPA,
  ChAD, OACIQ, CMQ, OIIQ, OPQ, OEQ). Tooltips show full disambiguating names.
- Critical (C2): raise text-brand-navy/40 -> /70 on footnote (2.69:1 -> 9:1
  contrast, passes WCAG AAA) and text-[10px] navy/50 -> text-xs navy/70 on
  monogram captions (12px minimum + AA contrast). Critical for legal
  disclosure legibility.
- Critical (C3): drop unverifiable '50 heures' specific number from
  methodology footnote — replaced with 'methodologie disponible sur demande'
  (defensible without committing to numbers we can't verify).
- Important (I1): use &nbsp; before %/$ in KPI numbers per OQLF French
  typography rules + |safe filter to render entities.
- Important (I2): replace fragile substring-strip test with explicit
  forbidden-phrase list (RECONNU PAR, ENDOSSÉ PAR, etc.). Update ordre
  list test + footnote test to match new wording.
2026-04-27 17:35:43 -04:00
Allison
2a7e142b03 feat(marketing): trust bar with 9 ordres pros + 4 KPIs + methodology footnote
- Section AFTER hero, white bg with brand-border y-borders
- 9 monogram placeholders (gradient circles with initials, NOT official
  logos to avoid licensing issues + false-endorsement exposure)
  hover from opacity-50 to opacity-100 for subtle interaction
- Eyebrow phrasing 'MAPPÉ AUX 9 ORDRES PROFESSIONNELS' (factual scope,
  not 'CERTIFIÉ PAR' which would be a false-endorsement claim under
  LPC art. 219 / Competition Act s. 52)
- 4 KPIs with grad-text numbers: ~5 min/heure, 95%+ FR-CA, 0$ par user,
  100% local — each with a 1-line context line and a small subtext
- Methodology footnote: 'Précision mesurée sur 50 heures d'audio
  interne, détails sur demande' — defensible disclosure for the 95%
  claim (LPC art. 219 hygiene)
- 4 new tests verify ordres list, factual phrasing, KPIs, footnote
2026-04-27 17:27:03 -04:00
Allison
b24a0f064d fix(marketing): WCAG 2.3.3 reduced-motion + defensible social proof + em-spacing
- Add @media (prefers-reduced-motion: reduce) override to disable animations
  for vestibular-sensitive users (covers hero + future scroll/infinite anims)
- Replace 5-star icon + '27 cabinets' claim with shield + 'Conçu avec 9
  ordres professionnels' + 'Pré-inscription ouverte' (LPC art. 219 +
  Competition Act s. 52 compliance — pre-launch claims must be factual)
- Convert H1/H2/H3 letter-spacing from absolute px to em-relative (-3px →
  -0.028em on H1) so tracking scales correctly with clamp font-size on mobile
- Update test_hero_has_social_proof_microcopy to assert new copy

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:19:16 -04:00
Allison
03af2a516d feat(marketing): hero section with cosmic orbs + dual CTA + social proof
- 3 radial-gradient orbs (blue 16%, cyan 7%, green 11%) + subtle 40px
  grid + horizontal accent line — FlexiHub cosmic background signature
- H1 clamp(2.5rem,4vw,4rem) font-black with grad-text accent on
  'sans risquer votre permis' tagline
- Eyebrow 11px tracking-[0.18em] grad-text — 'TRANSCRIPTION IA · CONFORME LOI 25 · QUÉBEC'
- Sub-headline ≤25 words declares Loi 25 compliance + 9 ordres pros
- Dual CTA: Réserver une démo (primary gradient+glow) + Voir les tarifs
  → (ghost) — drives demo conversion + pricing self-service
- Social proof microcopy above-the-fold: 5★ + 27 cabinets + Lancement
  printemps 2026
- Staggered tc-fade-in-up animations 0/75/150/300/400ms with
  animation-fill-mode: backwards (no FOIT)
- 6 new tests verify H1 grad-text, dual CTA, orb opacities, social proof,
  staggered delays, eyebrow messaging
2026-04-27 17:13:02 -04:00
Allison
89e2fd29d1 fix(marketing): button macro safety + placeholder assets + mobile login
- Button macro: variants/sizes use .get() with primary/md fallback (no KeyError),
  add as_button=True parameter for form submit CTAs (forms in B-2.2 signup,
  B-2.7 checkout)
- Header: drop hidden sm:inline-block on Connexion link so login is always
  visible from mobile (avoids UX dead-end before A-2.7 hamburger lands)
- Add placeholder static/images/favicon.svg (brand gradient + 'D')
- Add placeholder 1x1 PNG static/images/og/og-default.png (replaced in A-3.3
  with real 1200x630 branded OG image)
2026-04-27 17:02:15 -04:00
Allison
49bf94576c feat(marketing): base.html layout + glassmorphism header + button macro
- templates/macros/button.html: 3 variants (primary gradient/glow, secondary,
  ghost) x 3 sizes for reuse across marketing/billing/legal/auth templates
- templates/marketing/base.html: Tailwind v4-scoped layout with FlexiHub
  glassmorphism header (62px, navy/.97, backdrop-blur-xl, .045 border),
  sticky positioning, OG/Twitter meta, Inter font preload, marketing.css
  link, Alpine.js defer, 5-item main nav + Connexion/Demarrer CTAs
- templates/marketing/_footer.html: minimal Phase 2 placeholder with
  legal links + Inverness QC address + info@dictia.ca (full footer in A-2.7)
- templates/marketing/landing.html: minimal hero placeholder (replaced
  in A-2.2 with full hero + cosmic orbs)
- src/marketing/routes.py: landing() now render_template instead of inline HTML
- 7 tests verify template structure, FlexiHub markers, nav, CTAs, legal
  links, no login redirect for anonymous users
- Tailwind CSS rebuilt with new template content scope (cssnano-minified)
2026-04-27 16:51:06 -04:00
Allison
08318a946f fix(marketing): RFC 9309 robots.txt semantics + lazy marketing import
- Remove redundant named User-agent blocks (Googlebot, Bingbot, ClaudeBot,
  GPTBot, PerplexityBot, Applebot) that per RFC 9309 §2.2 overrode the
  wildcard and granted those bots access to /api/, /admin, /account.
- Add explicit Google-Extended and ChatGPT-User blocks (AI opt-in
  signaling) with full Allow/Disallow rule sets.
- Fix /blog → /blog/ for prefix-match consistency.
- Move src.marketing.routes import inside recordings.index() function
  to localize cross-blueprint dependency (was at module top, inverting
  initialization order).
- Add shadow-warning comment at marketing_bp registration site.
2026-04-27 16:43:02 -04:00
Allison
1071e56173 feat(marketing): exempt public blueprints from noindex + fix / route collision
- add_no_crawl_headers now skips marketing.*, legal.*, billing.success,
  static, and robots_txt endpoints via _is_public_indexable_endpoint
  helper; all other routes keep the X-Robots-Tag noindex header
- recordings.index drops @login_required and instead redirects
  anonymous users to marketing.landing, resolving the URL-map
  collision between recordings_bp and marketing_bp at "/"
- robots.txt rewritten: public marketing pages and /legal/* allowed,
  /api/, /admin, /account, /share/, /app/, /checkout, /login, /signup,
  /webhooks/ disallowed; Googlebot, Bingbot, ClaudeBot, GPTBot,
  PerplexityBot, Applebot explicitly allowed
- New tests/test_no_crawl_headers.py (14 tests) covers exemption
  helper + integration on /, /robots.txt, /static, /admin, /login
- New tests/test_marketing_root_redirect.py (4 tests) verifies
  anonymous users at / never get a /login redirect

Tests verified via AST + logic walkthrough; pytest blocked on Windows
by pre-existing fcntl import in src/init_db.py (B-1.2 limitation).
2026-04-27 16:28:55 -04:00
Allison
571890e692 feat(marketing): self-host Alpine.js 3 2026-04-27 15:47:23 -04:00
Allison
3ca542fe40 feat(marketing): self-host Inter Variable + JetBrains Mono (Loi 25) 2026-04-27 15:40:52 -04:00
Allison
31948aec01 fix(marketing): enable cssnano minification + npm ci for reproducibility 2026-04-27 15:32:05 -04:00
Allison
b27b3c1d44 feat(marketing): bootstrap Tailwind v4 + design tokens FlexiHub
Adds Tailwind v4 / PostCSS pipeline that compiles to static/css/marketing.css,
to be loaded only by future templates/marketing/** templates and to coexist
with the existing legacy v3 JIT runtime used by index.html / account.html /
admin.html. The legacy v3 runtime stays untouched.

- package.json: postcss-cli build:css and watch:css scripts
- postcss.config.js: @tailwindcss/postcss + autoprefixer
- static/css/tailwind.config.js: brand tokens (b1/b2/b3, navy, navy2, navy3,
  bg, border), font families, brand-grad, cta shadows, FlexiHub keyframes
  (tc-fade-in-up/right, tc-float-y, tc-pulse-glow, plus-breathe). content
  paths scoped to marketing/legal/billing/macros only - purge cannot touch
  legacy templates.
- static/css/input.css: @import "tailwindcss"; + @config directive (Tailwind
  v4 backward-compat for v3-style JS config). @font-face Inter Variable +
  JetBrains Mono Variable (woff2). base layer body font/color, h1-h3
  letter-spacing. utilities: grad-text, grad-bg, eyebrow.
- Dockerfile: new stage 3 'assets-builder' (node:20-alpine) compiles CSS;
  runtime stage copies the built file in via --from=assets-builder, after
  COPY . . so the freshly-built file always wins.
- .gitignore + .dockerignore: exclude node_modules.

Build verified locally: marketing.css = 121 KB minified (Tailwind v4 ships
all default theme tokens + reset properties even with empty content;
realistic baseline, will not grow much as marketing markup is added).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 15:23:25 -04:00
InnovA AI
42772a31ed Initial release: DictIA v0.8.14-alpha (fork de Speakr, AGPL-3.0) 2026-03-16 21:47:37 +00:00