Files
intotheeast-com-content/themes/intotheeast/templates/story.html.twig
T
m038 5bc8d008df fix(story): end_date format Y-m-d H:i; fix guard comparison; remove test data
Blueprint: end_date format changed to Y-m-d H:i (same as start date) so
Admin2 uses the identical datepicker — avoids ambiguous d-m-Y input being
misread by PHP as m-d-Y.

Template guard: was comparing end_date string against page.date|date('Y-m-d')
which can never match. Now compares date-only parts of both fields:
page.header.end_date|date('Y-m-d') != page.date|date('Y-m-d')

Montalcino live page: removed test end_date '12-09-2026 00:00'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 12:10:21 +02:00

268 lines
11 KiB
Twig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'partials/base.html.twig' %}
{% block nav %}
<a class="story-escape" href="{{ page.parent().url }}" onclick="if(history.length > 1){ history.back(); return false; }">← Back</a>
<span class="story-nav-title" id="story-nav-title" aria-hidden="true">{{ page.title }}</span>
{% endblock %}
{% block content %}
{% set hero = null %}
{% if page.header.hero_image and page.media[page.header.hero_image] is defined %}
{% set hero = page.media[page.header.hero_image] %}
{% endif %}
{% set date_str = page.date|date('d M Y') %}
{% if page.header.end_date and page.header.end_date|date('Y-m-d') != page.date|date('Y-m-d') %}
{% set sd = page.date|date('d') %}
{% set sm = page.date|date('M') %}
{% set sy = page.date|date('Y') %}
{% set ed = page.header.end_date|date('d') %}
{% set em = page.header.end_date|date('M') %}
{% set ey = page.header.end_date|date('Y') %}
{% if sy == ey and sm == em %}
{% set date_str = sd ~ ' ' ~ ed ~ ' ' ~ em ~ ' ' ~ ey %}
{% elseif sy == ey %}
{% set date_str = sd ~ ' ' ~ sm ~ ' ' ~ ed ~ ' ' ~ em ~ ' ' ~ ey %}
{% else %}
{% set date_str = sd ~ ' ' ~ sm ~ ' ' ~ sy ~ ' ' ~ ed ~ ' ' ~ em ~ ' ' ~ ey %}
{% endif %}
{% endif %}
{% set location = page.header.location_name ?? '' %}
<div class="story-hero" id="story-hero">
<div class="story-hero__img-wrap">
{% if hero %}
<img src="{{ hero.url }}" alt="{{ page.header.hero_alt ?? page.title }}" class="story-hero__img" loading="eager">
{% else %}
<div class="story-hero__img-placeholder"></div>
{% endif %}
</div>
<div class="story-hero__overlay" id="story-overlay" aria-hidden="true"></div>
<div class="story-hero__content">
<h1 class="story-hero__title">{{ page.title }}</h1>
<p class="story-hero__meta">
<time datetime="{{ page.date|date('Y-m-d') }}">{{ date_str }}</time>
{% if location %} · {{ location }}{% endif %}
</p>
</div>
<div class="story-hero__scroll-cue" id="story-scroll-cue" aria-hidden="true">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"/>
</svg>
</div>
<div class="story-hero__spacer"></div>
</div>
<div class="story-body">
{{ page.content|raw }}
<footer class="story-footer">
<a href="{{ page.parent().url }}" onclick="if(history.length > 1){ history.back(); return false; }">← Back</a>
</footer>
</div>
<button class="story-totop" id="story-totop" aria-label="Back to top">↑ Top</button>
<script src="https://cdn.jsdelivr.net/npm/scrollama@3/build/scrollama.min.js"></script>
<script>
/* ── Hero scroll effect ──────────────────────────────────── */
(function () {
var overlay = document.getElementById('story-overlay');
var cue = document.getElementById('story-scroll-cue');
var hidden = false;
var ticking = false;
if (!overlay) return;
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
function update() {
var scrollY = window.scrollY;
var vh = window.innerHeight;
var heroEnd = vh * 1.4; // img-wrap (100vh) + spacer (40vh)
var mid = heroEnd * 0.5; // peak darkness at 70vh
var opacity = scrollY <= mid
? (scrollY / mid) * 0.65
: Math.max(0, (1 - (scrollY - mid) / (heroEnd - mid)) * 0.65);
overlay.style.background = 'rgba(0,0,0,' + opacity.toFixed(3) + ')';
overlay.style.display = (opacity < 0.002) ? 'none' : '';
if (!hidden && window.scrollY > 80) {
cue.classList.add('is-hidden');
hidden = true;
}
ticking = false;
}
window.addEventListener('scroll', function () {
if (!ticking) { requestAnimationFrame(update); ticking = true; }
}, { passive: true });
update();
})();
/* ── Story title in nav (cross-fades as hero content exits viewport top) ── */
(function () {
var navTitle = document.getElementById('story-nav-title');
var heroContent = document.querySelector('.story-hero__content');
if (!navTitle || !heroContent) return;
var hasBeenVisible = false;
var ticking = false;
function update() {
var rect = heroContent.getBoundingClientRect();
var inView = rect.bottom > 0 && rect.top < window.innerHeight;
if (inView) hasBeenVisible = true;
if (hasBeenVisible) {
var opacity;
if (rect.bottom <= 0) {
opacity = 1; // fully above viewport
} else if (rect.top <= 0) {
opacity = 1 - rect.bottom / rect.height; // partially exiting top
} else {
opacity = 0; // fully in viewport
}
navTitle.style.opacity = opacity.toFixed(3);
}
ticking = false;
}
window.addEventListener('scroll', function () {
if (!ticking) { requestAnimationFrame(update); ticking = true; }
}, { passive: true });
update();
})();
/* ── Back to top button ─────────────────────────────────── */
(function () {
var btn = document.getElementById('story-totop');
if (!btn) return;
var threshold = window.innerHeight * 0.8;
var shown = false;
btn.addEventListener('click', function () {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
window.addEventListener('scroll', function () {
var shouldShow = window.scrollY > threshold;
if (shouldShow !== shown) {
shown = shouldShow;
btn.classList.toggle('is-visible', shown);
}
}, { passive: true });
})();
/* ── ChapterBreak scroll-reveal ─────────────────────────── */
(function () {
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var panels = document.querySelectorAll('.chapter-break__panel');
if (!panels.length) return;
if (reduced) { panels.forEach(function (p) { p.classList.add('is-revealed'); }); return; }
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) { e.target.classList.add('is-revealed'); io.unobserve(e.target); }
});
}, { threshold: 0.25 });
panels.forEach(function (p) { io.observe(p); });
})();
/* ── PullQuote scroll-reveal ─────────────────────────────── */
(function () {
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var quotes = document.querySelectorAll('.pull-quote');
if (!quotes.length) return;
if (reduced) { quotes.forEach(function (q) { q.classList.add('is-revealed'); }); return; }
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) { e.target.classList.add('is-revealed'); io.unobserve(e.target); }
});
}, { threshold: 0.2 });
quotes.forEach(function (q) { io.observe(q); });
})();
/* ── SnapGallery dot indicator ───────────────────────────── */
(function () {
document.querySelectorAll('.pgallery').forEach(function (gallery) {
var slides = gallery.querySelectorAll('.pgallery__slide');
var dots = gallery.querySelectorAll('.pgallery__dot');
if (!slides.length || !dots.length) return;
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) {
var idx = parseInt(e.target.dataset.index, 10);
dots.forEach(function (d) { d.classList.remove('is-active'); });
if (dots[idx]) dots[idx].classList.add('is-active');
}
});
}, { threshold: 0.5, root: gallery.querySelector('.pgallery__frame') });
slides.forEach(function (s) { io.observe(s); });
});
})();
/* ── ScrollySection (Scrollama) ──────────────────────────── */
(function () {
var reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var sections = document.querySelectorAll('.scrolly');
if (!sections.length || typeof scrollama === 'undefined') return;
var panOffsets = ['50% 40%', '50% 50%', '50% 60%', '50% 45%', '50% 55%'];
sections.forEach(function (section) {
var img = section.querySelector('.scrolly__img');
var overlay = section.querySelector('.scrolly__img-overlay');
var contentEl = section.querySelector('.scrolly__steps-content');
var stepsWrap = section.querySelector('.scrolly__steps');
if (!img || !contentEl || !stepsWrap) return;
/* Split slot content on <hr> into individual step divs */
var nodes = Array.from(contentEl.childNodes);
var groups = [];
var curr = [];
nodes.forEach(function (n) {
if (n.nodeName === 'HR') { if (curr.length) groups.push(curr); curr = []; }
else curr.push(n);
});
if (curr.length) groups.push(curr);
if (!groups.length) groups.push(nodes);
groups.forEach(function (group) {
var step = document.createElement('div');
step.className = 'scrolly-step';
var inner = document.createElement('div');
inner.className = 'scrolly-step__inner';
group.forEach(function (n) { inner.appendChild(n.cloneNode(true)); });
step.appendChild(inner);
stepsWrap.appendChild(step);
});
if (reduced) {
section.querySelectorAll('.scrolly-step').forEach(function (s) { s.classList.add('is-active'); });
return;
}
/* Initial blur */
img.style.filter = 'blur(8px)';
img.style.transform = 'scale(1.04)';
img.style.transition = 'filter 0.7s cubic-bezier(.16,1,.3,1), transform 0.7s cubic-bezier(.16,1,.3,1), object-position 1.2s cubic-bezier(.16,1,.3,1)';
new IntersectionObserver(function (entries, obs) {
if (entries[0].isIntersecting) {
img.style.filter = 'blur(0)';
img.style.transform = 'scale(1)';
obs.disconnect();
}
}, { threshold: 0.1 }).observe(section);
scrollama()
.setup({ step: Array.from(section.querySelectorAll('.scrolly-step')), offset: 0.55 })
.onStepEnter(function (d) {
d.element.classList.add('is-active');
img.style.objectPosition = panOffsets[d.index % panOffsets.length];
if (overlay) overlay.style.background = 'rgba(0,0,0,' + (0.05 + (d.index % 3) * 0.05) + ')';
})
.onStepExit(function (d) {
if (d.direction === 'up') d.element.classList.remove('is-active');
});
});
})();
</script>
{% endblock %}