nordabiz/templates/announcements/detail.html
Maciej Pienczyn cebe52f303 refactor: Rebranding i aktualizacja modelu AI
- Zmiana nazwy: "Norda Biznes Hub" → "Norda Biznes Partner"
- Aktualizacja modelu AI: Gemini 2.0 Flash → Gemini 3 Flash
- Zachowano historyczne odniesienia w timeline i dokumentacji

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 14:08:39 +01:00

515 lines
16 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ announcement.title }} - Aktualnosci - Norda Biznes Partner{% endblock %}
{% block meta_description %}{{ announcement.excerpt or announcement.content|striptags|truncate(160) }}{% endblock %}
{% block extra_css %}
<style>
.back-link {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
color: var(--text-secondary);
text-decoration: none;
font-size: var(--font-size-sm);
margin-bottom: var(--spacing-lg);
}
.back-link:hover {
color: var(--primary);
}
.announcement-layout {
display: grid;
grid-template-columns: 1fr 300px;
gap: var(--spacing-2xl);
}
@media (max-width: 992px) {
.announcement-layout {
grid-template-columns: 1fr;
}
}
.announcement-main {
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
overflow: hidden;
}
.announcement-header {
padding: var(--spacing-xl);
border-bottom: 1px solid var(--border);
}
.announcement-meta {
display: flex;
align-items: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-md);
flex-wrap: wrap;
}
.category-badge {
display: inline-block;
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
font-weight: 600;
background: var(--primary-bg);
color: var(--primary);
}
.category-event { background: #e0f2fe; color: #0369a1; }
.category-opportunity { background: #dcfce7; color: #15803d; }
.category-member_news { background: #fef3c7; color: #b45309; }
.category-partnership { background: #f3e8ff; color: #7c3aed; }
.meta-date {
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.meta-views {
color: var(--text-muted);
font-size: var(--font-size-sm);
}
.meta-author {
color: var(--text-secondary);
font-size: var(--font-size-sm);
font-weight: 500;
}
.announcement-title {
font-size: var(--font-size-3xl);
font-weight: 700;
color: var(--text-primary);
line-height: 1.2;
margin-bottom: var(--spacing-md);
}
.announcement-excerpt {
font-size: var(--font-size-lg);
color: var(--text-secondary);
line-height: 1.5;
}
.announcement-image {
width: 100%;
max-height: 400px;
object-fit: cover;
}
.announcement-content {
padding: var(--spacing-xl);
}
.announcement-content p {
margin-bottom: var(--spacing-md);
line-height: 1.8;
}
.announcement-content h3 {
margin-top: var(--spacing-xl);
margin-bottom: var(--spacing-md);
font-size: var(--font-size-xl);
color: var(--text-primary);
}
.announcement-content ul, .announcement-content ol {
margin-bottom: var(--spacing-md);
padding-left: var(--spacing-xl);
}
.announcement-content li {
margin-bottom: var(--spacing-sm);
line-height: 1.6;
}
.announcement-content a {
color: var(--primary);
}
.announcement-content a:hover {
text-decoration: underline;
}
.external-link-box {
background: var(--primary-bg);
border: 1px solid var(--primary);
border-radius: var(--radius-lg);
padding: var(--spacing-lg);
margin: var(--spacing-xl) 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-md);
flex-wrap: wrap;
}
.external-link-box .link-text {
font-weight: 500;
color: var(--text-primary);
}
.external-link-box .btn {
white-space: nowrap;
}
.sidebar {
position: sticky;
top: var(--spacing-xl);
}
.sidebar-section {
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
padding: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
}
.sidebar-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-md);
padding-bottom: var(--spacing-sm);
border-bottom: 2px solid var(--primary);
}
.other-announcement {
padding: var(--spacing-md) 0;
border-bottom: 1px solid var(--border);
}
.other-announcement:last-child {
border-bottom: none;
padding-bottom: 0;
}
.other-announcement:first-child {
padding-top: 0;
}
.other-announcement a {
color: var(--text-primary);
text-decoration: none;
font-weight: 500;
line-height: 1.4;
display: block;
}
.other-announcement a:hover {
color: var(--primary);
}
.other-announcement .date {
font-size: var(--font-size-xs);
color: var(--text-muted);
margin-top: var(--spacing-xs);
}
.share-buttons {
display: flex;
gap: var(--spacing-sm);
}
.share-btn {
width: 40px;
height: 40px;
border-radius: var(--radius);
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
font-size: 1.2em;
transition: transform 0.2s ease;
}
.share-btn:hover {
transform: scale(1.1);
}
.share-btn.facebook { background: #1877f2; color: white; }
.share-btn.linkedin { background: #0a66c2; color: white; }
.share-btn.twitter { background: #1da1f2; color: white; }
.share-btn.copy { background: var(--surface-secondary); color: var(--text-primary); border: 1px solid var(--border); }
.pinned-notice {
background: linear-gradient(135deg, var(--primary-bg) 0%, var(--surface) 100%);
border: 1px solid var(--primary);
border-radius: var(--radius);
padding: var(--spacing-sm) var(--spacing-md);
margin-bottom: var(--spacing-lg);
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: var(--font-size-sm);
color: var(--primary);
font-weight: 500;
}
/* Seen by section */
.seen-by-section {
margin-top: var(--spacing-xl);
padding-top: var(--spacing-lg);
border-top: 1px solid var(--border);
}
.seen-by-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-md);
}
.seen-by-title {
font-size: var(--font-size-sm);
font-weight: 600;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.seen-by-stats {
font-size: var(--font-size-xs);
color: var(--text-muted);
background: var(--background);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-full);
}
.seen-by-avatars {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-xs);
}
.reader-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-sm);
font-weight: 600;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
position: relative;
}
.reader-avatar:hover {
transform: scale(1.1);
box-shadow: var(--shadow-md);
z-index: 10;
}
.reader-avatar[data-tooltip]:hover::after {
content: attr(data-tooltip);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: var(--text-primary);
color: white;
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
font-size: var(--font-size-xs);
white-space: nowrap;
margin-bottom: 4px;
z-index: 100;
}
.reader-avatar.more {
background: var(--surface-secondary);
color: var(--text-secondary);
border: 1px solid var(--border);
}
.progress-bar-container {
width: 100%;
height: 6px;
background: var(--border);
border-radius: 3px;
margin-top: var(--spacing-sm);
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary) 0%, var(--success) 100%);
border-radius: 3px;
transition: width 0.5s ease;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<a href="{{ url_for('announcements_list') }}" class="back-link">
&larr; Powrot do listy ogloszen
</a>
{% if announcement.is_pinned %}
<div class="pinned-notice">
&#128204; To ogloszenie jest przypiete i wyswietla sie na gorze listy
</div>
{% endif %}
<div class="announcement-layout">
<!-- Main content -->
<article class="announcement-main">
<div class="announcement-header">
<div class="announcement-meta">
<span class="category-badge category-{{ announcement.category }}">
{{ category_labels.get(announcement.category, announcement.category) }}
</span>
<span class="meta-date">
{{ announcement.published_at.strftime('%d %B %Y') if announcement.published_at else '' }}
</span>
{% if announcement.author %}
<span class="meta-author">
&#128100; {{ announcement.author.name }}
</span>
{% endif %}
<span class="meta-views">
&#128065; {{ announcement.views_count or 0 }} wyswietlen
</span>
</div>
<h1 class="announcement-title">{{ announcement.title }}</h1>
{% if announcement.excerpt %}
<p class="announcement-excerpt">{{ announcement.excerpt }}</p>
{% endif %}
</div>
{% if announcement.image_url %}
<img src="{{ announcement.image_url }}" alt="{{ announcement.title }}" class="announcement-image"
onerror="this.style.display='none'">
{% endif %}
<div class="announcement-content">
{{ announcement.content|safe }}
{% if announcement.external_link %}
<div class="external-link-box">
<div class="link-text">
&#127760; Wiecej informacji znajdziesz na zewnetrznej stronie
</div>
<a href="{{ announcement.external_link }}" target="_blank" rel="noopener noreferrer" class="btn btn-primary">
Przejdz do strony &rarr;
</a>
</div>
{% endif %}
<!-- Seen by section -->
<div class="seen-by-section">
<div class="seen-by-header">
<div class="seen-by-title">
&#128065; Przeczytane przez
</div>
<div class="seen-by-stats">
{{ readers_count }} z {{ total_users }} ({{ read_percentage }}%)
</div>
</div>
<div class="seen-by-avatars">
{% for read in readers[:20] %}
<div class="reader-avatar"
data-tooltip="{{ read.user.name or read.user.email.split('@')[0] }}{% if loop.first %} (Ty){% endif %}"
style="background: hsl({{ (read.user.id * 137) % 360 }}, 65%, 50%);">
{{ (read.user.name or read.user.email)[0]|upper }}
</div>
{% endfor %}
{% if readers_count > 20 %}
<div class="reader-avatar more" data-tooltip="i {{ readers_count - 20 }} innych">
+{{ readers_count - 20 }}
</div>
{% endif %}
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" style="width: {{ read_percentage }}%;"></div>
</div>
</div>
</div>
</article>
<!-- Sidebar -->
<aside class="sidebar">
<!-- Share -->
<div class="sidebar-section">
<h3 class="sidebar-title">Udostepnij</h3>
<div class="share-buttons">
<a href="https://www.facebook.com/sharer/sharer.php?u={{ request.url|urlencode }}"
target="_blank" rel="noopener" class="share-btn facebook" title="Udostepnij na Facebooku">
f
</a>
<a href="https://www.linkedin.com/sharing/share-offsite/?url={{ request.url|urlencode }}"
target="_blank" rel="noopener" class="share-btn linkedin" title="Udostepnij na LinkedIn">
in
</a>
<a href="https://twitter.com/intent/tweet?url={{ request.url|urlencode }}&text={{ announcement.title|urlencode }}"
target="_blank" rel="noopener" class="share-btn twitter" title="Udostepnij na Twitterze">
X
</a>
<button class="share-btn copy" title="Kopiuj link" onclick="copyLink()">
&#128279;
</button>
</div>
</div>
<!-- Other announcements -->
{% if other_announcements %}
<div class="sidebar-section">
<h3 class="sidebar-title">Inne ogloszenia</h3>
{% for other in other_announcements %}
<div class="other-announcement">
<a href="{{ url_for('announcement_detail', slug=other.slug) }}">
{{ other.title }}
</a>
<div class="date">
{{ other.published_at.strftime('%d.%m.%Y') if other.published_at else '' }}
</div>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Back to list -->
<div class="sidebar-section" style="text-align: center;">
<a href="{{ url_for('announcements_list') }}" class="btn btn-secondary" style="width: 100%;">
&larr; Wszystkie ogloszenia
</a>
</div>
</aside>
</div>
</div>
{% endblock %}
{% block extra_js %}
function copyLink() {
navigator.clipboard.writeText(window.location.href).then(function() {
const btn = document.querySelector('.share-btn.copy');
const originalText = btn.innerHTML;
btn.innerHTML = '&#10003;';
btn.style.background = 'var(--success-bg)';
btn.style.color = 'var(--success)';
setTimeout(function() {
btn.innerHTML = originalText;
btn.style.background = '';
btn.style.color = '';
}, 2000);
});
}
{% endblock %}