Implements docs/read-markets-compliance-changes.md as flag-gated changes (no deletions) so paused features stay in the tree for future re-enable. All four flags default False so a fresh deploy is compliance-safe. - New env flags: PORTFOLIO_AI_ENABLED, PORTFOLIO_SYNC_ENABLED, TICKER_UNIVERSE_AGGREGATE_ENABLED, SUBSCRIPTIONS_ENABLED. - Gates: /api/analyze, /api/portfolio/sync*, /api/stripe/*, /pricing, ticker_universe writes, portfolio_analysis.analyse(). is_paid_active() returns True for any auth'd user when subscriptions are paused. - Prompts (PROMPT_VERSION 10): universal _COMPLIANCE_RIDER prepended to every system prompt; watch list removed; price-target / close-above-below / trigger / forward-state-as-description rules added; SPECULATIVE pivoted to regime-only scenarios; daily + weekly digests tightened. - Reviewer: deterministic regex/lexicon pre-check fail-closed under the Haiku call; portfolio rider gated by PORTFOLIO_AI_ENABLED; base prompt sharpened for forward-state and MAR forward-opinion patterns; ReviewerVerdict audit table; generate_with_review retry helper. - Migration 0026: purge portfolio_sync + ticker_universe; create reviewer_verdicts. - Copy: MAR cite fixed to Art 3(1)(35) + Art 20 + Del Reg 2016/958; portfolio reframed as browser-only viewer in disclaimer / privacy / terms / about / pricing / landing (en + it). TODO(legal) marker for lawyer sign-off on disclaimer. - Tests: 13 lexicon + 6 reviewer compliance regressions; conftest enables all flags so existing 402 tests still cover their code paths. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
148 lines
6.1 KiB
HTML
148 lines
6.1 KiB
HTML
{% extends "public_base.html" %}
|
|
{% block title %}{{ BRAND_NAME }} · {{ t.hero.tagline }}{% endblock %}
|
|
|
|
{% block main %}
|
|
|
|
<section class="hero">
|
|
<div class="hero__brand">{{ BRAND_NAME }}</div>
|
|
<h1 class="hero__headline">{{ t.hero.tagline }}</h1>
|
|
<p class="hero__subhead">{{ t.hero.subhead | safe }}</p>
|
|
<div class="hero__ctas">
|
|
{% if cu and (cu.user or cu.is_admin) %}
|
|
<a class="btn-primary" href="/">{{ t.hero.cta_dashboard }}</a>
|
|
{% if SUBSCRIPTIONS_ENABLED %}<a class="btn-secondary" href="/pricing">{{ t.hero.cta_pricing }}</a>{% endif %}
|
|
{% else %}
|
|
<a class="btn-primary" href="/login">{{ t.hero.cta_signup }}</a>
|
|
{% if SUBSCRIPTIONS_ENABLED %}<a class="btn-secondary" href="/pricing">{{ t.hero.cta_pricing }}</a>{% endif %}
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="shot-hero">
|
|
<button class="shot shot--hero"
|
|
data-full="{{ url_for('static', path='/images/dashboard.png') }}?v={{ ASSET_VERSION }}"
|
|
data-alt="{{ t.shot_dashboard.alt }}"
|
|
data-caption="{{ t.shot_dashboard.caption }}">
|
|
<img src="{{ url_for('static', path='/images/dashboard.png') }}?v={{ ASSET_VERSION }}"
|
|
alt="{{ t.shot_dashboard.alt }}" loading="lazy">
|
|
<span class="shot__zoom" aria-hidden="true">{{ t.shot_dashboard.zoom_hint }}</span>
|
|
</button>
|
|
</section>
|
|
|
|
<section class="feature-grid">
|
|
<div class="feature-card">
|
|
<div class="feature-card__tag">{{ t.features.news.tag }}</div>
|
|
<h3 class="feature-card__title">{{ t.features.news.title }}</h3>
|
|
<p class="feature-card__body">{{ t.features.news.body | safe }}</p>
|
|
<button class="shot feature-card__shot"
|
|
data-full="{{ url_for('static', path='/images/news-feed.png') }}?v={{ ASSET_VERSION }}"
|
|
data-alt="{{ t.features.news.shot_alt }}"
|
|
data-caption="{{ t.features.news.shot_caption }}">
|
|
<img src="{{ url_for('static', path='/images/news-feed.png') }}?v={{ ASSET_VERSION }}"
|
|
alt="{{ t.features.news.shot_alt }}" loading="lazy">
|
|
</button>
|
|
</div>
|
|
|
|
<div class="feature-card">
|
|
<div class="feature-card__tag">{{ t.features.indicators.tag }}</div>
|
|
<h3 class="feature-card__title">{{ t.features.indicators.title }}</h3>
|
|
<p class="feature-card__body">{{ t.features.indicators.body | safe }}</p>
|
|
<button class="shot feature-card__shot"
|
|
data-full="{{ url_for('static', path='/images/indicators-read.png') }}?v={{ ASSET_VERSION }}"
|
|
data-alt="{{ t.features.indicators.shot_alt }}"
|
|
data-caption="{{ t.features.indicators.shot_caption }}">
|
|
<img src="{{ url_for('static', path='/images/indicators-read.png') }}?v={{ ASSET_VERSION }}"
|
|
alt="{{ t.features.indicators.shot_alt }}" loading="lazy">
|
|
</button>
|
|
</div>
|
|
|
|
<div class="feature-card">
|
|
<div class="feature-card__tag">{{ t.features.strategic.tag }}</div>
|
|
<h3 class="feature-card__title">{{ t.features.strategic.title }}</h3>
|
|
<p class="feature-card__body">{{ t.features.strategic.body | safe }}</p>
|
|
<button class="shot feature-card__shot"
|
|
data-full="{{ url_for('static', path='/images/strategic-log.png') }}?v={{ ASSET_VERSION }}"
|
|
data-alt="{{ t.features.strategic.shot_alt }}"
|
|
data-caption="{{ t.features.strategic.shot_caption }}">
|
|
<img src="{{ url_for('static', path='/images/strategic-log.png') }}?v={{ ASSET_VERSION }}"
|
|
alt="{{ t.features.strategic.shot_alt }}" loading="lazy">
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="public-section" style="text-align:center;">
|
|
<p style="font-size: 14px; color: var(--muted); max-width: 60ch; margin: 0 auto;">
|
|
{{ t.multilang_callout | safe }}
|
|
</p>
|
|
</section>
|
|
|
|
<section class="public-section shots-section">
|
|
<h2 class="public-section__head">{{ t.more_views.head }}</h2>
|
|
<div class="shots-grid">
|
|
<button class="shot"
|
|
data-full="{{ url_for('static', path='/images/chat-with-log.png') }}?v={{ ASSET_VERSION }}"
|
|
data-alt="{{ t.more_views.chat.alt }}"
|
|
data-caption="{{ t.more_views.chat.caption }}">
|
|
<img src="{{ url_for('static', path='/images/chat-with-log.png') }}?v={{ ASSET_VERSION }}"
|
|
alt="{{ t.more_views.chat.alt }}" loading="lazy">
|
|
<div class="shot__caption">
|
|
<strong>{{ t.more_views.chat.caption_strong }}</strong>
|
|
<span>{{ t.more_views.chat.caption_span }}</span>
|
|
</div>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="public-section">
|
|
<p style="font-size: 13.5px; color: var(--muted);">{{ t.portfolio_blurb | safe }}</p>
|
|
</section>
|
|
|
|
<section class="not-strip">
|
|
<strong>{{ t.not_strip.head }}</strong>
|
|
<ul>
|
|
{% for item in t.not_strip.items %}<li>{{ item }}</li>{% endfor %}
|
|
</ul>
|
|
</section>
|
|
|
|
<section class="public-section">
|
|
<p style="font-size: 13px; color: var(--muted);">{{ t.footer.legal | safe }}</p>
|
|
<div class="hero__ctas" style="margin-top:8px;">
|
|
{% if cu and (cu.user or cu.is_admin) %}
|
|
<a class="btn-primary" href="/">{{ t.hero.cta_dashboard }}</a>
|
|
{% else %}
|
|
<a class="btn-primary" href="/login">{{ t.hero.cta_signup }}</a>
|
|
{% endif %}
|
|
</div>
|
|
</section>
|
|
|
|
<dialog id="shot-modal" class="shot-modal" aria-label="Screenshot preview">
|
|
<button class="shot-modal__close" type="button" aria-label="Close">×</button>
|
|
<img id="shot-modal__img" alt="">
|
|
<p id="shot-modal__caption"></p>
|
|
</dialog>
|
|
|
|
<script>
|
|
(function () {
|
|
var dlg = document.getElementById('shot-modal');
|
|
var img = document.getElementById('shot-modal__img');
|
|
var cap = document.getElementById('shot-modal__caption');
|
|
if (!dlg || !dlg.showModal) return; // gracefully skip on ancient browsers
|
|
document.querySelectorAll('.shot').forEach(function (btn) {
|
|
btn.addEventListener('click', function () {
|
|
img.src = btn.dataset.full;
|
|
img.alt = btn.dataset.alt || '';
|
|
cap.textContent = btn.dataset.caption || '';
|
|
dlg.showModal();
|
|
});
|
|
});
|
|
// Backdrop click closes; clicking the image itself does not.
|
|
dlg.addEventListener('click', function (e) {
|
|
if (e.target === dlg) dlg.close();
|
|
});
|
|
dlg.querySelector('.shot-modal__close').addEventListener('click', function () {
|
|
dlg.close();
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
{% endblock %}
|