Files
intotheeast-com-content/themes/intotheeast/templates/story.html.twig
T
m038 13d6576a2c fix(story): smart date range formatting + blueprint end_date format fix
Blueprint: end_date format changed from 'Y-m-d H:i' to 'Y-m-d' — the
demo frontmatter stores dates without a time component so Admin was
failing to parse it and showing the field empty.

Template: three-case smart range formatting:
- Same month & year  → 01 – 03 Sep 2025
- Same year only     → 01 Jan – 03 Sep 2025
- Different years    → 01 Jan 2025 – 03 Feb 2026

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vgmzx8VTTTmCskSpQtsLTr
2026-06-20 11:50:44 +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 %}
{% 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 %}