diff --git a/template-visual/annotated-document/README.md b/template-visual/annotated-document/README.md new file mode 100644 index 00000000..53951c49 --- /dev/null +++ b/template-visual/annotated-document/README.md @@ -0,0 +1,8 @@ +## A Template for an Annotated Document Article + +To do: + - Externalize text in `index.html` to some other file so it's easier to find/change + - Make `style.css` more readable - CEV: working on highlighting the important parts of file + - Try to figure out a way to make annotations less manual (i.e. not requiring as much tinkering in the console) + - Comment everything thoroughly - CEV: worked on config.js showing were comments are used - Pending review. + - Write instructions on how to use this template in this file \ No newline at end of file diff --git a/template-visual/annotated-document/index.html b/template-visual/annotated-document/index.html new file mode 100644 index 00000000..39bff83d --- /dev/null +++ b/template-visual/annotated-document/index.html @@ -0,0 +1,163 @@ + +
+
+
+
+
+
+
+
+
+
+ + In 1967, University President George Beadle appointed a + faculty committee—chaired by First Amendment scholar + Harry Kalven Jr.—to prepare “a statement on the + University’s role in political and social action.” The + resulting report articulates the University’s commitment + to institutional neutrality. By abstaining from speech + about political and social issues, the report says, the + University allows its individual members the fullest + freedom of expression. +
++ Journalist Jamie Kalven, Harry Kalven Jr.’s son, sat + down with the Maroon to walk through the Kalven + Report. In the 14 years he spent editing his father’s + manuscript on the First Amendment and the American + tradition of freedom of speech, Jamie Kalven reviewed + hundreds of Harry Kalven Jr.’s papers to + familiarize + himself with his father’s thinking. +
++ Providing context on his father’s writing, Jamie Kalven + argues that we, now in a moment of attacks on academic + freedom and higher education, should return to “the + point of departure” the document provides. +
+ Reading from a letter addressed to the members of the + drafting committee, which you’ll find publicly available for + the first time on the Maroon’s website, Jamie Kalven + discusses where his father decided to “spend the emphasis” + in the report. Harry Kalven Jr. concluded that, in 1967, + stating the general principle against collective action was + more important than foregrounding the exceptional cases in + which collective action may have been appropriate. ++ Still, there will be exceptions, moments when the “very + mission of the University and its values of free + inquiry” are under threat. +
++ Jamie Kalven + believes + that, today, we find ourselves in one such moment. +
++ Listen to the Maroon’s conversation with Jamie + Kalven + here. +
` +}; + +sessionStorage.setItem('annotations', JSON.stringify(annotations)); +sessionStorage.setItem('content', JSON.stringify(content)); diff --git a/template-visual/annotated-document/js/main.js b/template-visual/annotated-document/js/main.js new file mode 100644 index 00000000..5cd57ec9 --- /dev/null +++ b/template-visual/annotated-document/js/main.js @@ -0,0 +1,151 @@ +// inspired by https://www.nytimes.com/interactive/2025/02/13/us/doc-annotation-memo-from-bove.html + +// -------------- constants -------------- +let active_highlight = document.querySelector('.highlight.active'); +let next_highlight; +let annotations; +let currentHighlightIndex = 1; +let isMobile = window.innerWidth < 1075; +let mobileOffset = isMobile ? 30 : 0; + +// isMobile = true; + +if (isMobile) { + //change logo to black + document.querySelector('#maroon-logo').src = + 'static/images/transparent-nameplate-small.png'; +} +// -------------- waypoints -------------- + +function createWaypoint() { + for (let i = 1; i < 18; i++) { + new Waypoint({ + element: document.querySelector(`#highlight-${i}`), + handler: function (direction) { + if (direction == 'down') { + if (i != 1) { + // make active + next_highlight = document.querySelector( + `#highlight-${i}` + ); + activateNextHighlight(next_highlight); + currentHighlightIndex = parseInt(i); + + updateAnnotationCard(annotations[i]); + } + } else { + if (i == 1) { + // make active + next_highlight = document.querySelector( + `#highlight-${i}` + ); + activateNextHighlight(next_highlight); + currentHighlightIndex = parseInt(i); + } else { + next_highlight = document.querySelector( + `#highlight-${i - 1}` + ); + activateNextHighlight(next_highlight); + + updateAnnotationCard(annotations[i - 1]); + } + } + }, + offset: offset(i) + }); + } +} + +// ------ functions --------- + +function offset(i) { + if (i <= 3) { + value = 55 - mobileOffset; + } else { + value = 45 - mobileOffset; + } + return String(value) + '%'; +} + +function updateAnnotationCard(newText) { + const annotationCardElement = document.querySelector('.annotation-card'); + const annotationTextElement = document.querySelector( + '.annotation-card-text' + ); + + // Add fade-out class to the entire card + annotationCardElement.classList.add('fade-out'); + + // Wait for the fade-out animation to complete + setTimeout(() => { + // Update the text content + annotationTextElement.innerHTML = newText; + + // Remove fade-out class and add fade-in class to the entire card + annotationCardElement.classList.remove('fade-out'); + annotationCardElement.classList.add('fade-in'); + + // Remove fade-in class after the animation completes + setTimeout(() => { + annotationCardElement.classList.remove('fade-in'); + }, 200); + }, 200); +} + +// function to deactive current highlight and active next highlight +function activateNextHighlight(next_highlight) { + if (active_highlight.classList.contains('active')) { + active_highlight.classList.remove('active'); + } + active_highlight = next_highlight; + active_highlight.classList.add('active'); +} + +// -------- event listeners --------- + +function addEventListeners() { + // Add event listener for keydown events to switch between highlights + document.addEventListener('keydown', (event) => { + if (event.key === 'ArrowRight') { + console.log(currentHighlightIndex); + currentHighlightIndex++; + const nextHighlight = document.querySelector( + `#highlight-${currentHighlightIndex}` + ); + activateNextHighlight(nextHighlight); + updateAnnotationCard(annotations[currentHighlightIndex]); + } else if (event.key === 'ArrowLeft') { + currentHighlightIndex--; + const prevHighlight = document.querySelector( + `#highlight-${currentHighlightIndex}` + ); + activateNextHighlight(prevHighlight); + updateAnnotationCard(annotations[currentHighlightIndex]); + } + }); +} + +// ------- init -------- + +function init() { + content = JSON.parse(sessionStorage.getItem('content')); + for (const key in content) { + if (content[key] != null) { + console.log('Setting ' + key); + document.getElementById(key).innerHTML = content[key]; + } + } + + annotations = JSON.parse(sessionStorage.getItem('annotations')); + document.querySelector('.annotation-card-text').innerHTML = annotations[1]; + createWaypoint(); + addEventListeners(); +} + +document.addEventListener('DOMContentLoaded', () => { + // force scroll to top on refresh + window.onbeforeunload = function () { + window.scrollTo(0, 0); + }; + init(); +}); diff --git a/template-visual/annotated-document/js/noframework.waypoints.min.js b/template-visual/annotated-document/js/noframework.waypoints.min.js new file mode 100644 index 00000000..3979ced1 --- /dev/null +++ b/template-visual/annotated-document/js/noframework.waypoints.min.js @@ -0,0 +1,513 @@ +/*! +Waypoints - 4.0.1 +Copyright © 2011-2016 Caleb Troughton +Licensed under the MIT license. +https://github.com/imakewebthings/waypoints/blob/master/licenses.txt +*/ +!(function () { + 'use strict'; + function t(n) { + if (!n) throw new Error('No options passed to Waypoint constructor'); + if (!n.element) + throw new Error('No element option passed to Waypoint constructor'); + if (!n.handler) + throw new Error('No handler option passed to Waypoint constructor'); + (this.key = 'waypoint-' + e), + (this.options = t.Adapter.extend({}, t.defaults, n)), + (this.element = this.options.element), + (this.adapter = new t.Adapter(this.element)), + (this.callback = n.handler), + (this.axis = this.options.horizontal ? 'horizontal' : 'vertical'), + (this.enabled = this.options.enabled), + (this.triggerPoint = null), + (this.group = t.Group.findOrCreate({ + name: this.options.group, + axis: this.axis + })), + (this.context = t.Context.findOrCreateByElement( + this.options.context + )), + t.offsetAliases[this.options.offset] && + (this.options.offset = t.offsetAliases[this.options.offset]), + this.group.add(this), + this.context.add(this), + (i[this.key] = this), + (e += 1); + } + var e = 0, + i = {}; + (t.prototype.queueTrigger = function (t) { + this.group.queueTrigger(this, t); + }), + (t.prototype.trigger = function (t) { + this.enabled && this.callback && this.callback.apply(this, t); + }), + (t.prototype.destroy = function () { + this.context.remove(this), + this.group.remove(this), + delete i[this.key]; + }), + (t.prototype.disable = function () { + return (this.enabled = !1), this; + }), + (t.prototype.enable = function () { + return this.context.refresh(), (this.enabled = !0), this; + }), + (t.prototype.next = function () { + return this.group.next(this); + }), + (t.prototype.previous = function () { + return this.group.previous(this); + }), + (t.invokeAll = function (t) { + var e = []; + for (var n in i) e.push(i[n]); + for (var o = 0, r = e.length; r > o; o++) e[o][t](); + }), + (t.destroyAll = function () { + t.invokeAll('destroy'); + }), + (t.disableAll = function () { + t.invokeAll('disable'); + }), + (t.enableAll = function () { + t.Context.refreshAll(); + for (var e in i) i[e].enabled = !0; + return this; + }), + (t.refreshAll = function () { + t.Context.refreshAll(); + }), + (t.viewportHeight = function () { + return window.innerHeight || document.documentElement.clientHeight; + }), + (t.viewportWidth = function () { + return document.documentElement.clientWidth; + }), + (t.adapters = []), + (t.defaults = { + context: window, + continuous: !0, + enabled: !0, + group: 'default', + horizontal: !1, + offset: 0 + }), + (t.offsetAliases = { + 'bottom-in-view': function () { + return this.context.innerHeight() - this.adapter.outerHeight(); + }, + 'right-in-view': function () { + return this.context.innerWidth() - this.adapter.outerWidth(); + } + }), + (window.Waypoint = t); +})(), + (function () { + 'use strict'; + function t(t) { + window.setTimeout(t, 1e3 / 60); + } + function e(t) { + (this.element = t), + (this.Adapter = o.Adapter), + (this.adapter = new this.Adapter(t)), + (this.key = 'waypoint-context-' + i), + (this.didScroll = !1), + (this.didResize = !1), + (this.oldScroll = { + x: this.adapter.scrollLeft(), + y: this.adapter.scrollTop() + }), + (this.waypoints = { vertical: {}, horizontal: {} }), + (t.waypointContextKey = this.key), + (n[t.waypointContextKey] = this), + (i += 1), + o.windowContext || + ((o.windowContext = !0), (o.windowContext = new e(window))), + this.createThrottledScrollHandler(), + this.createThrottledResizeHandler(); + } + var i = 0, + n = {}, + o = window.Waypoint, + r = window.onload; + (e.prototype.add = function (t) { + var e = t.options.horizontal ? 'horizontal' : 'vertical'; + (this.waypoints[e][t.key] = t), this.refresh(); + }), + (e.prototype.checkEmpty = function () { + var t = this.Adapter.isEmptyObject(this.waypoints.horizontal), + e = this.Adapter.isEmptyObject(this.waypoints.vertical), + i = this.element == this.element.window; + t && + e && + !i && + (this.adapter.off('.waypoints'), delete n[this.key]); + }), + (e.prototype.createThrottledResizeHandler = function () { + function t() { + e.handleResize(), (e.didResize = !1); + } + var e = this; + this.adapter.on('resize.waypoints', function () { + e.didResize || + ((e.didResize = !0), o.requestAnimationFrame(t)); + }); + }), + (e.prototype.createThrottledScrollHandler = function () { + function t() { + e.handleScroll(), (e.didScroll = !1); + } + var e = this; + this.adapter.on('scroll.waypoints', function () { + (!e.didScroll || o.isTouch) && + ((e.didScroll = !0), o.requestAnimationFrame(t)); + }); + }), + (e.prototype.handleResize = function () { + o.Context.refreshAll(); + }), + (e.prototype.handleScroll = function () { + var t = {}, + e = { + horizontal: { + newScroll: this.adapter.scrollLeft(), + oldScroll: this.oldScroll.x, + forward: 'right', + backward: 'left' + }, + vertical: { + newScroll: this.adapter.scrollTop(), + oldScroll: this.oldScroll.y, + forward: 'down', + backward: 'up' + } + }; + for (var i in e) { + var n = e[i], + o = n.newScroll > n.oldScroll, + r = o ? n.forward : n.backward; + for (var s in this.waypoints[i]) { + var l = this.waypoints[i][s]; + if (null !== l.triggerPoint) { + var a = n.oldScroll < l.triggerPoint, + h = n.newScroll >= l.triggerPoint, + p = a && h, + u = !a && !h; + (p || u) && + (l.queueTrigger(r), (t[l.group.id] = l.group)); + } + } + } + for (var d in t) t[d].flushTriggers(); + this.oldScroll = { + x: e.horizontal.newScroll, + y: e.vertical.newScroll + }; + }), + (e.prototype.innerHeight = function () { + return this.element == this.element.window + ? o.viewportHeight() + : this.adapter.innerHeight(); + }), + (e.prototype.remove = function (t) { + delete this.waypoints[t.axis][t.key], this.checkEmpty(); + }), + (e.prototype.innerWidth = function () { + return this.element == this.element.window + ? o.viewportWidth() + : this.adapter.innerWidth(); + }), + (e.prototype.destroy = function () { + var t = []; + for (var e in this.waypoints) + for (var i in this.waypoints[e]) + t.push(this.waypoints[e][i]); + for (var n = 0, o = t.length; o > n; n++) t[n].destroy(); + }), + (e.prototype.refresh = function () { + var t, + e = this.element == this.element.window, + i = e ? void 0 : this.adapter.offset(), + n = {}; + this.handleScroll(), + (t = { + horizontal: { + contextOffset: e ? 0 : i.left, + contextScroll: e ? 0 : this.oldScroll.x, + contextDimension: this.innerWidth(), + oldScroll: this.oldScroll.x, + forward: 'right', + backward: 'left', + offsetProp: 'left' + }, + vertical: { + contextOffset: e ? 0 : i.top, + contextScroll: e ? 0 : this.oldScroll.y, + contextDimension: this.innerHeight(), + oldScroll: this.oldScroll.y, + forward: 'down', + backward: 'up', + offsetProp: 'top' + } + }); + for (var r in t) { + var s = t[r]; + for (var l in this.waypoints[r]) { + var a, + h, + p, + u, + d, + f = this.waypoints[r][l], + c = f.options.offset, + w = f.triggerPoint, + y = 0, + g = null == w; + f.element !== f.element.window && + (y = f.adapter.offset()[s.offsetProp]), + 'function' == typeof c + ? (c = c.apply(f)) + : 'string' == typeof c && + ((c = parseFloat(c)), + f.options.offset.indexOf('%') > -1 && + (c = Math.ceil( + (s.contextDimension * c) / 100 + ))), + (a = s.contextScroll - s.contextOffset), + (f.triggerPoint = Math.floor(y + a - c)), + (h = w < s.oldScroll), + (p = f.triggerPoint >= s.oldScroll), + (u = h && p), + (d = !h && !p), + !g && u + ? (f.queueTrigger(s.backward), + (n[f.group.id] = f.group)) + : !g && d + ? (f.queueTrigger(s.forward), + (n[f.group.id] = f.group)) + : g && + s.oldScroll >= f.triggerPoint && + (f.queueTrigger(s.forward), + (n[f.group.id] = f.group)); + } + } + return ( + o.requestAnimationFrame(function () { + for (var t in n) n[t].flushTriggers(); + }), + this + ); + }), + (e.findOrCreateByElement = function (t) { + return e.findByElement(t) || new e(t); + }), + (e.refreshAll = function () { + for (var t in n) n[t].refresh(); + }), + (e.findByElement = function (t) { + return n[t.waypointContextKey]; + }), + (window.onload = function () { + r && r(), e.refreshAll(); + }), + (o.requestAnimationFrame = function (e) { + var i = + window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + t; + i.call(window, e); + }), + (o.Context = e); + })(), + (function () { + 'use strict'; + function t(t, e) { + return t.triggerPoint - e.triggerPoint; + } + function e(t, e) { + return e.triggerPoint - t.triggerPoint; + } + function i(t) { + (this.name = t.name), + (this.axis = t.axis), + (this.id = this.name + '-' + this.axis), + (this.waypoints = []), + this.clearTriggerQueues(), + (n[this.axis][this.name] = this); + } + var n = { vertical: {}, horizontal: {} }, + o = window.Waypoint; + (i.prototype.add = function (t) { + this.waypoints.push(t); + }), + (i.prototype.clearTriggerQueues = function () { + this.triggerQueues = { up: [], down: [], left: [], right: [] }; + }), + (i.prototype.flushTriggers = function () { + for (var i in this.triggerQueues) { + var n = this.triggerQueues[i], + o = 'up' === i || 'left' === i; + n.sort(o ? e : t); + for (var r = 0, s = n.length; s > r; r += 1) { + var l = n[r]; + (l.options.continuous || r === n.length - 1) && + l.trigger([i]); + } + } + this.clearTriggerQueues(); + }), + (i.prototype.next = function (e) { + this.waypoints.sort(t); + var i = o.Adapter.inArray(e, this.waypoints), + n = i === this.waypoints.length - 1; + return n ? null : this.waypoints[i + 1]; + }), + (i.prototype.previous = function (e) { + this.waypoints.sort(t); + var i = o.Adapter.inArray(e, this.waypoints); + return i ? this.waypoints[i - 1] : null; + }), + (i.prototype.queueTrigger = function (t, e) { + this.triggerQueues[e].push(t); + }), + (i.prototype.remove = function (t) { + var e = o.Adapter.inArray(t, this.waypoints); + e > -1 && this.waypoints.splice(e, 1); + }), + (i.prototype.first = function () { + return this.waypoints[0]; + }), + (i.prototype.last = function () { + return this.waypoints[this.waypoints.length - 1]; + }), + (i.findOrCreate = function (t) { + return n[t.axis][t.name] || new i(t); + }), + (o.Group = i); + })(), + (function () { + 'use strict'; + function t(t) { + return t === t.window; + } + function e(e) { + return t(e) ? e : e.defaultView; + } + function i(t) { + (this.element = t), (this.handlers = {}); + } + var n = window.Waypoint; + (i.prototype.innerHeight = function () { + var e = t(this.element); + return e ? this.element.innerHeight : this.element.clientHeight; + }), + (i.prototype.innerWidth = function () { + var e = t(this.element); + return e ? this.element.innerWidth : this.element.clientWidth; + }), + (i.prototype.off = function (t, e) { + function i(t, e, i) { + for (var n = 0, o = e.length - 1; o > n; n++) { + var r = e[n]; + (i && i !== r) || t.removeEventListener(r); + } + } + var n = t.split('.'), + o = n[0], + r = n[1], + s = this.element; + if (r && this.handlers[r] && o) + i(s, this.handlers[r][o], e), (this.handlers[r][o] = []); + else if (o) + for (var l in this.handlers) + i(s, this.handlers[l][o] || [], e), + (this.handlers[l][o] = []); + else if (r && this.handlers[r]) { + for (var a in this.handlers[r]) + i(s, this.handlers[r][a], e); + this.handlers[r] = {}; + } + }), + (i.prototype.offset = function () { + if (!this.element.ownerDocument) return null; + var t = this.element.ownerDocument.documentElement, + i = e(this.element.ownerDocument), + n = { top: 0, left: 0 }; + return ( + this.element.getBoundingClientRect && + (n = this.element.getBoundingClientRect()), + { + top: n.top + i.pageYOffset - t.clientTop, + left: n.left + i.pageXOffset - t.clientLeft + } + ); + }), + (i.prototype.on = function (t, e) { + var i = t.split('.'), + n = i[0], + o = i[1] || '__default', + r = (this.handlers[o] = this.handlers[o] || {}), + s = (r[n] = r[n] || []); + s.push(e), this.element.addEventListener(n, e); + }), + (i.prototype.outerHeight = function (e) { + var i, + n = this.innerHeight(); + return ( + e && + !t(this.element) && + ((i = window.getComputedStyle(this.element)), + (n += parseInt(i.marginTop, 10)), + (n += parseInt(i.marginBottom, 10))), + n + ); + }), + (i.prototype.outerWidth = function (e) { + var i, + n = this.innerWidth(); + return ( + e && + !t(this.element) && + ((i = window.getComputedStyle(this.element)), + (n += parseInt(i.marginLeft, 10)), + (n += parseInt(i.marginRight, 10))), + n + ); + }), + (i.prototype.scrollLeft = function () { + var t = e(this.element); + return t ? t.pageXOffset : this.element.scrollLeft; + }), + (i.prototype.scrollTop = function () { + var t = e(this.element); + return t ? t.pageYOffset : this.element.scrollTop; + }), + (i.extend = function () { + function t(t, e) { + if ('object' == typeof t && 'object' == typeof e) + for (var i in e) e.hasOwnProperty(i) && (t[i] = e[i]); + return t; + } + for ( + var e = Array.prototype.slice.call(arguments), + i = 1, + n = e.length; + n > i; + i++ + ) + t(e[0], e[i]); + return e[0]; + }), + (i.inArray = function (t, e, i) { + return null == e ? -1 : e.indexOf(t, i); + }), + (i.isEmptyObject = function (t) { + for (var e in t) return !1; + return !0; + }), + n.adapters.push({ name: 'noframework', Adapter: i }), + (n.Adapter = i); + })(); diff --git a/template-visual/annotated-document/static/images/black_m.png b/template-visual/annotated-document/static/images/black_m.png new file mode 100644 index 00000000..6e5302ab Binary files /dev/null and b/template-visual/annotated-document/static/images/black_m.png differ diff --git a/template-visual/annotated-document/static/images/cover_kr.webp b/template-visual/annotated-document/static/images/cover_kr.webp new file mode 100644 index 00000000..5b38656a Binary files /dev/null and b/template-visual/annotated-document/static/images/cover_kr.webp differ diff --git a/template-visual/annotated-document/static/images/letter1_kr.webp b/template-visual/annotated-document/static/images/letter1_kr.webp new file mode 100644 index 00000000..3395474c Binary files /dev/null and b/template-visual/annotated-document/static/images/letter1_kr.webp differ diff --git a/template-visual/annotated-document/static/images/letter2_kr.webp b/template-visual/annotated-document/static/images/letter2_kr.webp new file mode 100644 index 00000000..31e82198 Binary files /dev/null and b/template-visual/annotated-document/static/images/letter2_kr.webp differ diff --git a/template-visual/annotated-document/static/images/report1.png b/template-visual/annotated-document/static/images/report1.png new file mode 100644 index 00000000..62a4d55e Binary files /dev/null and b/template-visual/annotated-document/static/images/report1.png differ diff --git a/template-visual/annotated-document/static/images/report2.png b/template-visual/annotated-document/static/images/report2.png new file mode 100644 index 00000000..ad19291e Binary files /dev/null and b/template-visual/annotated-document/static/images/report2.png differ diff --git a/template-visual/annotated-document/static/images/report3.png b/template-visual/annotated-document/static/images/report3.png new file mode 100644 index 00000000..6bec3c86 Binary files /dev/null and b/template-visual/annotated-document/static/images/report3.png differ diff --git a/template-visual/annotated-document/static/images/transparent-nameplate-offwhite.png b/template-visual/annotated-document/static/images/transparent-nameplate-offwhite.png new file mode 100644 index 00000000..f9a0842b Binary files /dev/null and b/template-visual/annotated-document/static/images/transparent-nameplate-offwhite.png differ diff --git a/template-visual/annotated-document/static/images/transparent-nameplate-small.png b/template-visual/annotated-document/static/images/transparent-nameplate-small.png new file mode 100644 index 00000000..1d810a2c Binary files /dev/null and b/template-visual/annotated-document/static/images/transparent-nameplate-small.png differ diff --git a/template-visual/annotated-document/static/images/white_m.png b/template-visual/annotated-document/static/images/white_m.png new file mode 100644 index 00000000..53cc890b Binary files /dev/null and b/template-visual/annotated-document/static/images/white_m.png differ diff --git a/template-visual/annotated-document/static/style explained.txt b/template-visual/annotated-document/static/style explained.txt new file mode 100644 index 00000000..3df497f0 --- /dev/null +++ b/template-visual/annotated-document/static/style explained.txt @@ -0,0 +1,101 @@ +style.css controls how the article looks and where things are on the page. + +CSS is doing 3 things: + +# 1. Designing the article header + intro text (title, subtitle, etc). +# 2. Building the annotated-document layout: + the document pages / images + the overlay highlights / yellow boxes + the sticky annotation card / right side on desktop +# 3. Rearranging for mobile. + +At the top it loads Google Fonts: + - Montserrat + - Open Sans + - Playfair Display + +Then the named colors are used later (avoiding repeating hex codes). +:root { + --main-bg-color: #4d0000; + --maroon_color: #800000; +} + +Then making layouts predictable and removing default browser spacing: +# This affects everything # +* { box-sizing: border-box; } +body { margin:0; padding:0; font-family:'Georgia'; width:100%; } + + + +FIRST PART: Header section (the big split screen at the top): + + + +This part creates the “two-column hero”: +left: maroon background with title text +right: cover image + +More explanation needed for: + .header-container is display:flex and height:100vh (full screen). + .header-title-container and .header-img each get flex: 1 (half/half). + #title and #subtitle style typography (size, color, spacing). + + + +SECOND PART: Intro section (the “article text” before the document): + + +#intro-text { width: 50%; margin: auto; } - Centers the intro column on desktop.  +.article-text - Sets font size and line height  +link styles - Maroon color for hyperlinks inside intro.  + +# #intro-text and .article-text — they control readability. # + + + +THIRD PART: The annotated document layout + +This is for absolute positioning used later (for the annotation panel): +#content-container { + position: relative; +} + +This is how the scanned pages are positioned on desktop: +Important to highlight: left:-200px — this is a “layout hack” that future maintainers need to know exists. +#doc-container { + position: relative; + max-width: 800px; + left: -200px; +} + +max width controls how big the doc images appear +left:-200px shifts the doc left to make room for the annotation card + +# left:-200px — “layout hack” ### + +each page is: + .page { position: relative; } + inside it a .page-image img (the scan) + and a .page-highlights overlay on top + +FOURTH PART: Highlights (the yellow boxes over the document) + +Making the highlights layer match the image exactly. +.page-highlights { + position: absolute; + top:0; left:0; + width:100%; height:100%; +} + +The default is pale yellow, brighter with a border: +.highlight { background-color: #ffff1033; } +.highlight.active { border: 3px solid #ffe300; } + +Per-highlight positioning +Example: +#highlight-1 { left:10%; top:17%; width:78%; height:2.5%; } +These are the “coordinates” for each highlight box, relative to the image + +# Most important part of the document # +Each new article will need new highlight coordinates +if someone changes the page image size/aspect ratio it will break \ No newline at end of file diff --git a/template-visual/annotated-document/static/style.css b/template-visual/annotated-document/static/style.css new file mode 100644 index 00000000..dbe4e50f --- /dev/null +++ b/template-visual/annotated-document/static/style.css @@ -0,0 +1,646 @@ +@import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&family=Open+Sans:ital,wght@0,300..800;1,300..800&family=Playfair+Display:ital,wght@0,400..900;1,400..900&display=swap'); + +:root { + --main-bg-color: #4d0000; + --maroon_color: #800000; +} + +* { + box-sizing: border-box; +} + +body { + height: 100dvh; + margin: 0; + padding: 0; + font-family: 'Georgia', sans-serif; + width: 100%; +} + +.header-container { + display: flex; + height: 100vh; /* Full viewport height */ + margin-bottom: 50px; +} + +.header-title-container { + flex: 1; /* Takes up half the space */ + justify-content: center; /* Center horizontally */ + align-items: center; /* Center vertically */ + background-color: var(--main-bg-color); +} +.header-img { + flex: 1; /* Takes up half the space */ + display: flex; + justify-content: center; /* Center the image horizontally */ + align-items: center; /* Center the image vertically */ + background-color: #f0f0ef; +} + +.header-maroon { + margin-top: 10px; + width: 100%; + display: flex; + justify-content: center; +} + +.header-maroon img { + width: 220px; + margin: auto; +} + +#header-title { + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + max-width: 500px; + margin: auto; +} + +.header-img img { + width: 100%; /* Make sure the image doesn't overflow */ + height: 100%; /* Make sure the image doesn't overflow */ + object-fit: scale-down; /* Scale the image */ +} + +.intro hr { + opacity: 60%; +} + +#intro-text a { + color: #800000; + text-decoration: none; +} + +#intro-text a:hover { + text-decoration: underline; +} + +#intro-text { + width: 50%; + margin: auto; +} + +#title { + font-size: 45px; + font-weight: 500; + line-height: 1.3; + text-align: center; + margin-bottom: 15px; + color: white; +} + +#subtitle { + padding-block: 5px; + font-size: 22px; + text-align: center; + margin-bottom: 10px; + font-weight: 400; + line-height: 1.3; + color: white; +} + +#intro-text hr { + margin-top: 30px; + margin-bottom: 20px; + border: 1px solid rgba(0, 0, 0, 0.273); + opacity: 60%; +} + +#byline { + padding-block: 1px; + margin-block: 2px; + font-size: 14px; + text-align: left; +} + +.byline-link { + font-weight: bold; + text-decoration: none; + color: #800000; +} + +.byline-link:hover { + text-decoration: underline; +} + +.article-text { + font-family: Georgia, 'Times New Roman', Times, serif; + font-size: 20px; + line-height: 1.7; +} + +.title-box { + margin: auto; +} + +.title-box img { + width: 100%; + margin: auto; + display: block; + transform: rotate(270deg); +} + +#content-container { + border-top: 1px solid #800000; + background-color: rgb(128, 0, 0, 0.1); + margin-top: 50px; + padding-top: 60px; + padding-bottom: 60px; + position: relative; +} + +#doc-container { + position: relative; + margin: auto auto 28px; + max-width: 800px; + left: -200px; +} + +.page { + position: relative; + margin-bottom: 28px; +} + +#page-report { + background-color: #fcf9f6; +} + +#report { + padding: 100px; + font-family: 'Times New Roman', Times, serif; + font-size: 20px; + font-size: 18px; +} + +#report-title { + font-weight: 600; + text-transform: uppercase; +} + +.page-image img { + height: auto; + max-width: 100%; +} + +.page-image#report { + height: 500px; + width: 100%; + background-color: #800000; +} + +.page-highlights { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.highlight { + position: relative; + background-color: #ffff1033; + border: 2px solid transparent; + transition: all 0.1s ease; +} + +.highlight.active { + background-color: #ffff104d; + border: 3px solid #ffe300; +} + +#highlight-1 { + left: 10%; + top: 17%; + width: 78%; + height: 2.5%; +} + +#highlight-2 { + left: 10%; + top: 23.1%; + width: 78%; + height: 5%; +} + +#highlight-3 { + left: 27.3%; + top: 26.7%; + width: 26%; + height: 2.5%; +} + +#highlight-4 { + left: 53.2%; + top: 24.3%; + width: 28.4%; + height: 2.5%; +} + +#highlight-5 { + left: 10%; + top: 24%; + width: 79%; + height: 4.5%; +} + +#highlight-6 { + left: 19%; + top: 23.8%; + width: 67%; + height: 2.5%; +} + +#highlight-7 { + left: 15%; + top: 23.5%; + width: 61.5%; + height: 2.5%; +} + +#highlight-8 { + left: 10%; + top: 27.5%; + width: 79%; + height: 2.5%; +} + +#highlight-9 { + left: 10%; + top: 29.2%; + width: 79%; + height: 5%; +} + +#highlight-10 { + left: 10%; + top: 50.3%; + width: 79%; + height: 5%; +} + +#highlight-11 { + left: 10%; + top: 54%; + width: 79%; + height: 5%; +} + +#highlight-12 { + left: 10%; + top: 34.3%; + width: 79%; + height: 5%; +} + +#highlight-13 { + left: 10%; + top: 57%; + width: 79%; + height: 10%; +} + +#highlight-14 { + left: 10%; + top: 71.5%; + width: 79%; + height: 5%; +} + +#highlight-15 { + left: 10%; + top: 19%; + width: 79%; + height: 10%; +} + +#highlight-16 { + left: 10%; + top: 46%; + width: 79%; + height: 23%; +} + +#highlight-17 { + left: 8%; + top: 32%; + width: 84%; + height: 9%; +} + +.annotation-container { + left: calc(50% + 250px); + height: 100%; + top: 0; + position: absolute; +} + +.sticky { + position: sticky; + top: 40px; + height: calc(100vh - 40px); +} + +.annotation-card { + position: absolute; + top: 50%; + transform: translateY(-50%); + background-color: white; + transition: all 0.1s ease; +} + +.annotation-card-inner { + left: 0; + position: relative; + height: auto; + top: 0; + max-height: calc(100vh - 90px); + width: 400px; + padding: 18px; + border: none; + border-left: 5px solid #800000; +} + +.annotation-card-text { + padding: 0 20px; + font-size: 17px; + font-family: Georgia, 'Times New Roman', Times, serif; + color: #444; + animation: fadeIn 0.2s ease-in-out forwards; +} + +.annotation-card-text a { + color: #800000; + text-decoration: none; +} + +.annotation-card-text a:hover { + text-decoration: underline; +} + +#annotation-logo { + content: ''; + position: absolute; + bottom: -15px; + right: -15px; + width: 50px; + height: 50px; + border-radius: 10px; + background-color: rgba(128, 0, 0, 0.2); +} + +#annotation-logo-c { + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} + +#annotation-logo-c img { + width: 80%; +} + +#footer { + padding-block: 50px; +} + +.more-title { + font-size: 24px; + text-align: left; + font-family: Georgia, serif; + color: #000000; + font-weight: 400; + margin-bottom: 20px; +} + +.more-container { + width: 70%; + margin: auto; +} + +.more-articles { + display: flex; + justify-content: space-around; /* Distribute space evenly */ + align-items: stretch; /* Make all containers the same height */ + width: 100%; +} + +.article-container-outer { + display: flex; + flex-direction: column; + width: 30%; /* Adjust as needed for spacing */ +} + +.article-container-outer a { + text-decoration: none; +} + +.article-container { + display: flex; + flex-direction: column; + border: 1px solid #ccc; /* Optional: for visual separation */ + border-radius: 8px; /* Optional: for rounded corners */ + overflow: hidden; /* Ensure content doesn't overflow the container */ + background: #eeeeee; + height: 300px; +} + +.article-container:hover { + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} +.article-container:hover .article-img img { + transform: scale(1.1); +} + +.article-container a { + display: block; +} + +.article-img { + margin: auto; + width: 150px; + overflow: hidden; /* Clip images that exceed the container */ +} + +.article-img img { + width: 100%; + height: auto; + object-fit: cover; +} + +.article-title { + height: 50%; + font-family: Georgia, serif; + font-size: 18px; + line-height: 1.35em; + padding: 15px 15px; + color: #000000; + text-decoration: none; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +.fade-out { + animation: fadeOut 0.2s ease-in-out forwards; +} + +.fade-in { + animation: fadeIn 0.2s ease-in-out forwards; +} + +@media (max-width: 1350px) { + .annotation-container { + left: calc(50% + 120px); + } + + #doc-container { + max-width: 580px; + } +} + +@media screen and (max-width: 1075px) { + /* change header */ + + .header-container { + flex-direction: column; /* Stack the title container below the image */ + height: auto; /* Adjust height to fit content */ + position: relative; + margin-bottom: 20px; + } + + .header-img img { + width: auto; + max-height: 60vh; + } + + .header-title-container { + margin-top: 20px; + width: 100%; /* Take full width */ + order: 2; /* Push to the bottom */ + background-color: white; + } + + #title, + #subtitle { + color: black; + } + + .header-maroon { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 100%; + height: auto; + display: flex; + justify-content: center; + } + + .header-maroon img { + width: 200px; + height: auto; + } + + #header-title { + width: 90%; + } + + .annotation-container { + left: 0; + width: 100%; + } + + #content-container { + padding-bottom: 200px; + } + + #doc-container { + left: 0; + } + + .sticky { + top: 0; + } + .annotation-card { + top: 80%; + } + + .annotation-card-inner { + border: 5px solid #800000; + width: 100%; + } + + .annotation-card-text { + font-size: 15px; + } + + #annotation-logo { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 30px; + height: 30px; + border-radius: 10px; + background-color: rgba(128, 0, 0, 0.2); + } + + #intro-text { + width: 90%; + } + + #title { + font-size: 28px; + } + + .title-box img { + width: 50%; + margin-top: 10px; + } + + .title-container { + padding-top: 50px; + } + + .title-box { + flex-direction: column; + } + + #byline { + font-size: 13px; + } + + #subtitle { + font-size: 20px; + } + + .more-articles { + flex-direction: column; /* Stack article containers */ + } + + .article-container-outer { + width: 100%; + } + + .article-container { + margin-bottom: 20px; /* Add spacing between articles */ + } +}