Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions _data/gnarmpfsk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Archive documents shown on /about/gnarmpfsk. Files live in /docs/gnarmpfsk/.
#
# date: YYYY-MM-DD, used for sorting and the year filter.
# For approximate dates, pick a best guess and set date_display.
# date_display: optional; shown instead of the formatted date (e.g. "c. 1980").
# keywords: one or more of the keywords listed below.

keywords:
- Stories
- GNARMPFSK
- Winter School
- Cabins
- Other

documents:
- file: gnarmpfsk-1993-06.pdf
title: Gnarmpfsk, June 1993
date: "1993-06-17"
keywords: [GNARMPFSK, Cabins]

- file: gnarmpfsk-1993-winter.pdf
title: Gnarmpfsk, Winter 1993
date: "1993-01-01"
date_display: Winter 1993
keywords: [GNARMPFSK, Stories, Winter School, Cabins]

- file: gnarmpfsk-1979-10.pdf
title: GNARMPFSK, October 1979
date: "1979-10-04"
keywords: [GNARMPFSK, Cabins]

- file: gnarmpfsk-1981-03.pdf
title: GNARMPFSK, March 1981
date: "1981-03-01"
date_display: March 1981
keywords: [GNARMPFSK, Cabins]

- file: mitoc-flyer-c1980.pdf
title: MIT Outing Club flyer
date: "1980-06-01"
date_display: c. 1980
keywords: [Other]

- file: mitoc-flyer-1985.pdf
title: MIT Outing Club flyer for new students
date: "1985-09-01"
date_display: Fall 1985
keywords: [Cabins]

- file: winter-school-info.pdf
title: Winter Mountaineering School info
date: "1981-01-01"
date_display: c. 1981
keywords: [Winter School]

- file: winter-school-daytrip-checklist.pdf
title: Winter daytrip checklist
date: "1981-01-01"
date_display: c. 1981
keywords: [Winter School]

1 change: 1 addition & 0 deletions _data/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
title: Collier Adventure Grant
- /about/financial-aid
- /about/boston-rocks
- /about/gnarmpfsk
- items:
- /about/code-of-conduct
- href: /docs/advisoryboard/2025MITOCConstitution.pdf
Expand Down
102 changes: 102 additions & 0 deletions _includes/lightbox.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{% comment %}
Full-screen photo viewer. Include once on a page, then mark photo links with
data-lightbox="group-name": clicking one opens it full screen, and the arrows
(or arrow keys / swipe) move between photos with the same group name.
An optional data-caption="..." is shown under the photo.
{% endcomment %}
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Photo viewer" hidden>
<button type="button" class="lightbox-close" aria-label="Close">&times;</button>
<button type="button" class="lightbox-nav lightbox-prev" aria-label="Previous photo">&#8249;</button>
<figure>
<img alt="">
<figcaption></figcaption>
</figure>
<button type="button" class="lightbox-nav lightbox-next" aria-label="Next photo">&#8250;</button>
</div>

<style>
.lightbox { position: fixed; inset: 0; z-index: 2000; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.96); }
.lightbox[hidden] { display: none; }
.lightbox figure { margin: 0; max-width: calc(100vw - 140px); text-align: center; }
.lightbox img { max-width: 100%; max-height: calc(100vh - 90px); object-fit: contain; }
.lightbox figcaption { color: #ddd; margin-top: 10px; font-size: 15px; }
.lightbox button { position: absolute; border: 0; background: rgba(255, 255, 255, 0.12); color: #fff; line-height: 1; }
.lightbox button:hover, .lightbox button:focus-visible { background: rgba(255, 255, 255, 0.3); }
.lightbox-close { top: 12px; right: 12px; width: 44px; height: 44px; border-radius: 22px; font-size: 32px; }
.lightbox-nav { top: 50%; width: 52px; height: 72px; margin-top: -36px; border-radius: 6px; font-size: 48px; }
.lightbox-prev { left: 12px; }
.lightbox-next { right: 12px; }
.lightbox-nav[disabled] { visibility: hidden; }
@media (max-width: 600px) {
.lightbox figure { max-width: 100vw; }
.lightbox-nav { top: auto; bottom: 16px; margin-top: 0; }
}
</style>

<script>
(function () {
var box = document.getElementById('lightbox');
var img = box.querySelector('img');
var caption = box.querySelector('figcaption');
var prev = box.querySelector('.lightbox-prev');
var next = box.querySelector('.lightbox-next');
var items = [], index = 0, opener = null, touchX = null;

function show(i) {
index = i;
var link = items[i];
var thumb = link.querySelector('img');
img.src = link.getAttribute('href');
img.alt = thumb ? thumb.alt : '';
caption.textContent = link.getAttribute('data-caption') || '';
caption.hidden = !caption.textContent;
prev.disabled = i === 0;
next.disabled = i === items.length - 1;
// Preload neighbours so arrowing feels instant.
[i - 1, i + 1].forEach(function (j) { if (items[j]) new Image().src = items[j].getAttribute('href'); });
}

function open(link) {
var group = link.getAttribute('data-lightbox');
items = Array.prototype.slice.call(document.querySelectorAll('[data-lightbox="' + group + '"]'));
opener = link;
box.hidden = false;
document.body.style.overflow = 'hidden';
show(items.indexOf(link));
box.querySelector('.lightbox-close').focus();
}

function close() {
box.hidden = true;
img.removeAttribute('src');
document.body.style.overflow = '';
if (opener) opener.focus();
}

function step(d) { if (items[index + d]) show(index + d); }

document.addEventListener('click', function (e) {
var link = e.target.closest('[data-lightbox]');
if (!link || e.metaKey || e.ctrlKey || e.shiftKey) return;
e.preventDefault();
open(link);
});
prev.addEventListener('click', function () { step(-1); });
next.addEventListener('click', function () { step(1); });
box.querySelector('.lightbox-close').addEventListener('click', close);
box.addEventListener('click', function (e) { if (e.target === box || e.target.tagName === 'FIGURE') close(); });
document.addEventListener('keydown', function (e) {
if (box.hidden) return;
if (e.key === 'Escape') close();
else if (e.key === 'ArrowLeft') step(-1);
else if (e.key === 'ArrowRight') step(1);
});
box.addEventListener('touchstart', function (e) { touchX = e.touches[0].clientX; }, { passive: true });
box.addEventListener('touchend', function (e) {
if (touchX === null) return;
var dx = e.changedTouches[0].clientX - touchX;
if (Math.abs(dx) > 50) step(dx < 0 ? 1 : -1);
touchX = null;
});
})();
</script>
38 changes: 38 additions & 0 deletions _layouts/story.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
layout: base
---
{% comment %}
Layout for GNARMPFSK stories (_pages/about/gnarmpfsk/stories/).
Front matter: title, author, date ("YYYY-MM-DD"), keywords; optional date_display.
Photos: wrap an <img> in <a href="full.jpg" data-lightbox="story" data-caption="...">
to open it full screen; use <figure> for a single photo or <div class="story-photos">
for a grid.
{% endcomment %}
<article class="story">
<p class="story-back"><a href="/about/gnarmpfsk">&larr; Back to GNARMPFSK</a></p>
<header class="page-header">
<h1>{{ page.title }}</h1>
<p class="story-meta">
{% if page.author %}By {{ page.author }} &middot; {% endif %}
{% if page.date_display %}{{ page.date_display }}{% else %}{{ page.date | date: "%B %-d, %Y" }}{% endif %}
</p>
</header>
<div class="story-body">
{{ content }}
</div>
</article>

<style>
.story { max-width: 720px; margin: 0 auto; }
.story-back { margin-top: 20px; }
.story-meta { color: #777; margin: 8px 0 0; }
.story-body { font-size: 18px; line-height: 1.7; }
.story-body p { margin-bottom: 1.2em; }
.story-body figure { margin: 1.5em 0; }
.story-body figure img { display: block; width: 100%; border-radius: 2px; }
.story-body figcaption { color: #777; font-size: 14px; margin-top: 6px; }
.story-photos { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px; margin: 1.5em 0; }
.story-photos img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
</style>

{% include lightbox.html %}
180 changes: 180 additions & 0 deletions _pages/about/gnarmpfsk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
---
permalink: /about/gnarmpfsk
title: GNARMPFSK
# PDFs are listed in _data/gnarmpfsk.yml and hosted in /docs/gnarmpfsk/.
# Pages under _pages/about/gnarmpfsk/ with a `keywords` list (stories, photo galleries)
# are listed too.
---

GNARMPFSK---the sound made by a newly-awoken hiker in a wet sleeping bag on a frosty morning, as they realize that if they are to have any hot chocolate they are going to have to get up and make it themselves---is MITOC's newsletter, dating back to 1955!

Here, you can read (a selection of) old GNARMPFSK newsletters, as well as more recent stories from current and former MITOC'ers!

For other cool stories, also check out the [Sean A. Collier Adventure Grant](https://mitoc-cag.mit.edu/) or the [trip report archive (1946-2017)](/legacy-gallery/)!


<style>
.archive-controls { margin: 20px 0; }
.archive-controls .form-group { margin-right: 20px; margin-bottom: 10px; vertical-align: top; }
.archive-keywords .btn { margin: 0 4px 4px 0; }
.archive-list { display: flex; flex-wrap: wrap; margin: 0 -15px; }
.archive-doc { margin-bottom: 30px; }
.archive-doc h4 { margin-bottom: 4px; }
.archive-doc h4 a { color: inherit; }
.archive-meta { margin-bottom: 8px; color: #777; }
.archive-meta .label { margin-left: 4px; }
.archive-thumbs { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-bottom: 8px; }
.archive-thumbs img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; background: #eee; }
.archive-excerpt { font-size: 16px; line-height: 1.6; }
/* Two-handle year slider: two range inputs stacked on one track. */
.year-range { position: relative; width: 260px; max-width: 100%; height: 34px; }
.year-range-track { position: absolute; left: 0; right: 0; top: 15px; height: 4px; border-radius: 2px; background: #ddd; }
.year-range-fill { position: absolute; top: 0; bottom: 0; background: #337ab7; border-radius: 2px; }
.year-range input[type=range] { position: absolute; left: 0; top: 0; width: 100%; height: 34px; margin: 0; background: none; pointer-events: none; -webkit-appearance: none; appearance: none; }
.year-range input[type=range]::-webkit-slider-runnable-track { background: none; }
.year-range input[type=range]::-moz-range-track { background: none; }
.year-range input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; pointer-events: auto; width: 20px; height: 20px; border-radius: 50%; background: #fff; border: 2px solid #337ab7; cursor: pointer; }
.year-range input[type=range]::-moz-range-thumb { pointer-events: auto; width: 16px; height: 16px; border-radius: 50%; background: #fff; border: 2px solid #337ab7; cursor: pointer; }
.year-range input[type=range]:focus-visible { outline: none; }
.year-range input[type=range]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 3px rgba(51, 122, 183, 0.4); }
.year-range input[type=range]:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 3px rgba(51, 122, 183, 0.4); }
</style>

{% assign data = site.data.gnarmpfsk %}
{% assign pages = site.pages | where_exp: "p", "p.url contains '/about/gnarmpfsk/'" | where_exp: "p", "p.keywords" %}
{% assign docs = data.documents | concat: pages | sort: "date" | reverse %}

<form class="archive-controls form-inline" onsubmit="return false">
<div class="form-group">
<label>Keyword</label><br>
<div class="archive-keywords" role="group" id="archive-keywords">
<button type="button" class="btn btn-default active" data-keyword="" aria-pressed="true">All</button>
{% for keyword in data.keywords %}
<button type="button" class="btn btn-default" data-keyword="{{ keyword }}" aria-pressed="false">{{ keyword }}</button>
{% endfor %}
</div>
</div>
{% assign first_year = docs.last.date | slice: 0, 4 %}
{% assign last_year = docs.first.date | slice: 0, 4 %}
<div class="form-group">
<label id="archive-years-label">Years: <span id="archive-years-value">{{ first_year }}–{{ last_year }}</span></label><br>
<div class="year-range">
<div class="year-range-track"><div class="year-range-fill" id="archive-years-fill"></div></div>
<input type="range" id="archive-year-from" min="{{ first_year }}" max="{{ last_year }}" step="1" value="{{ first_year }}" aria-label="From year">
<input type="range" id="archive-year-to" min="{{ first_year }}" max="{{ last_year }}" step="1" value="{{ last_year }}" aria-label="To year">
</div>
</div>
<div class="form-group">
<label for="archive-sort">Sort by</label><br>
<select id="archive-sort" class="form-control">
<option value="date-desc">Date (newest first)</option>
<option value="date-asc">Date (oldest first)</option>
<option value="keyword">Keyword (A–Z)</option>
</select>
</div>
</form>

<p id="archive-count" class="text-muted">{{ docs.size }} documents</p>

<div class="archive-list" id="archive-list">
{% for doc in docs %}
{% if doc.file %}{% assign url = "/docs/gnarmpfsk/" | append: doc.file %}{% else %}{% assign url = doc.url %}{% endif %}
<div class="archive-doc col-xs-12 col-md-6" data-date="{{ doc.date }}" data-keywords="{{ doc.keywords | join: '|' }}" data-title="{{ doc.title | escape }}">
<h4><a href="{{ url }}">{{ doc.title }}</a></h4>
<div class="archive-meta">
Published {% if doc.date_display %}{{ doc.date_display }}{% else %}{{ doc.date | date: "%b. %-d, %Y" }}{% endif %}
{% for keyword in doc.keywords %}<span class="label label-primary">{{ keyword }}</span>{% endfor %}
</div>
{% if doc.file %}
<div class="embed-responsive" style="padding-bottom: 129%">
<iframe class="embed-responsive-item" src="{{ url }}#view=FitH&navpanes=0" title="{{ doc.title | escape }}" loading="lazy"></iframe>
</div>
<a href="{{ url }}">Open full size</a>
{% elsif doc.thumbnails %}
<a class="archive-thumbs" href="{{ url }}">
{% for thumb in doc.thumbnails %}<img src="{{ thumb }}" alt="" loading="lazy">{% endfor %}
</a>
<a href="{{ url }}">View all photos &rarr;</a>
{% else %}
<p class="archive-excerpt">{% if doc.author %}<em>By {{ doc.author }}.</em> {% endif %}{{ doc.content | markdownify | strip_html | truncatewords: 70 }}</p>
<a href="{{ url }}">Read the full story &rarr;</a>
{% endif %}
</div>
{% endfor %}
</div>

<p id="archive-empty" class="text-muted" style="display: none">No documents match these filters.</p>

<script>
(function () {
var list = document.getElementById('archive-list');
var docs = Array.prototype.slice.call(list.children);
var buttons = document.querySelectorAll('#archive-keywords .btn');
var yearFrom = document.getElementById('archive-year-from');
var yearTo = document.getElementById('archive-year-to');
var minYear = +yearFrom.min, maxYear = +yearFrom.max;
var sortSelect = document.getElementById('archive-sort');
var keyword = '';

function keywordsOf(el) { return el.getAttribute('data-keywords').split('|'); }
function byDateDesc(a, b) { return b.getAttribute('data-date').localeCompare(a.getAttribute('data-date')); }

function update() {
var from = +yearFrom.value, to = +yearTo.value;
var span = maxYear - minYear || 1;
var fill = document.getElementById('archive-years-fill');
fill.style.left = (from - minYear) / span * 100 + '%';
fill.style.right = (maxYear - to) / span * 100 + '%';
document.getElementById('archive-years-value').textContent = from === to ? from : from + '–' + to;
var sort = sortSelect.value;
var sorted = docs.slice().sort(function (a, b) {
if (sort === 'date-asc') return -byDateDesc(a, b);
if (sort === 'keyword') {
// Documents with the selected keyword sort by it; otherwise by their first keyword.
var ka = keyword && keywordsOf(a).indexOf(keyword) >= 0 ? keyword : keywordsOf(a)[0];
var kb = keyword && keywordsOf(b).indexOf(keyword) >= 0 ? keyword : keywordsOf(b)[0];
return ka.localeCompare(kb) || byDateDesc(a, b);
}
return byDateDesc(a, b);
});
var shown = 0;
// Reorder with CSS `order` rather than moving nodes, which would reload the iframes.
sorted.forEach(function (el, i) {
var match = (!keyword || keywordsOf(el).indexOf(keyword) >= 0) &&
+el.getAttribute('data-date').slice(0, 4) >= from &&
+el.getAttribute('data-date').slice(0, 4) <= to;
el.style.display = match ? '' : 'none';
el.style.order = i;
if (match) shown++;
});
document.getElementById('archive-count').textContent =
shown === docs.length ? docs.length + ' documents' : 'Showing ' + shown + ' of ' + docs.length + ' documents';
document.getElementById('archive-empty').style.display = shown ? 'none' : '';
}

Array.prototype.forEach.call(buttons, function (btn) {
btn.addEventListener('click', function () {
keyword = btn.getAttribute('data-keyword');
Array.prototype.forEach.call(buttons, function (b) {
b.classList.toggle('active', b === btn);
b.setAttribute('aria-pressed', b === btn ? 'true' : 'false');
});
update();
});
});
// Keep the handles from crossing; the one being dragged stops at the other.
yearFrom.addEventListener('input', function () {
if (+yearFrom.value > +yearTo.value) yearFrom.value = yearTo.value;
update();
});
yearTo.addEventListener('input', function () {
if (+yearTo.value < +yearFrom.value) yearTo.value = yearFrom.value;
update();
});
// When both handles sit at the same end, keep the one that can still move on top.
yearFrom.addEventListener('pointerdown', function () { yearFrom.style.zIndex = 2; yearTo.style.zIndex = 1; });
yearTo.addEventListener('pointerdown', function () { yearTo.style.zIndex = 2; yearFrom.style.zIndex = 1; });
update();
sortSelect.addEventListener('change', update);
})();
</script>
Loading