pulled in juxtapose to make modifications. now resizes properly. better

layout for both reviews and gallery
This commit is contained in:
2026-01-02 18:34:20 -05:00
parent 60b7fa4e80
commit dd5efd0b36
5 changed files with 1378 additions and 206 deletions
+347
View File
@@ -0,0 +1,347 @@
/* juxtapose - v1.2.2 - 2020-09-03
* Copyright (c) 2020 Alex Duner and Northwestern University Knight Lab
*/
div.juxtapose {
width: 100%;
font-family: Helvetica, Arial, sans-serif;
}
div.jx-slider {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
cursor: pointer;
color: #f3f3f3;
}
div.jx-handle {
position: absolute;
height: 100%;
width: 40px;
cursor: col-resize;
z-index: 15;
margin-left: -20px;
}
.vertical div.jx-handle {
height: 40px;
width: 100%;
cursor: row-resize;
margin-top: -20px;
margin-left: 0;
}
div.jx-control {
height: 100%;
margin-right: auto;
margin-left: auto;
width: 3px;
background-color: currentColor;
}
.vertical div.jx-control {
height: 3px;
width: 100%;
background-color: currentColor;
position: relative;
top: 50%;
transform: translateY(-50%);
}
div.jx-controller {
position: absolute;
margin: auto;
top: 0;
bottom: 0;
height: 60px;
width: 9px;
margin-left: -3px;
background-color: currentColor;
}
.vertical div.jx-controller {
height: 9px;
width: 100px;
margin-left: auto;
margin-right: auto;
top: -3px;
position: relative;
}
div.jx-arrow {
position: absolute;
margin: auto;
top: 0;
bottom: 0;
width: 0;
height: 0;
transition: all .2s ease;
}
.vertical div.jx-arrow {
position: absolute;
margin: 0 auto;
left: 0;
right: 0;
width: 0;
height: 0;
transition: all .2s ease;
}
div.jx-arrow.jx-left {
left: 2px;
border-style: solid;
border-width: 8px 8px 8px 0;
border-color: transparent currentColor transparent transparent;
}
div.jx-arrow.jx-right {
right: 2px;
border-style: solid;
border-width: 8px 0 8px 8px;
border-color: transparent transparent transparent currentColor;
}
.vertical div.jx-arrow.jx-left {
left: 0px;
top: 2px;
border-style: solid;
border-width: 0px 8px 8px 8px;
border-color: transparent transparent currentColor transparent;
}
.vertical div.jx-arrow.jx-right {
right: 0px;
top: auto;
bottom: 2px;
border-style: solid;
border-width: 8px 8px 0 8px;
border-color: currentColor transparent transparent transparent;
}
div.jx-handle:hover div.jx-arrow.jx-left,
div.jx-handle:active div.jx-arrow.jx-left {
left: -1px;
}
div.jx-handle:hover div.jx-arrow.jx-right,
div.jx-handle:active div.jx-arrow.jx-right {
right: -1px;
}
.vertical div.jx-handle:hover div.jx-arrow.jx-left,
.vertical div.jx-handle:active div.jx-arrow.jx-left {
left: 0px;
top: 0px;
}
.vertical div.jx-handle:hover div.jx-arrow.jx-right,
.vertical div.jx-handle:active div.jx-arrow.jx-right {
right: 0px;
bottom: 0px;
}
div.jx-image {
position: absolute;
height: 100%;
display: inline-block;
top: 0;
overflow: hidden;
-webkit-backface-visibility: hidden;
}
.vertical div.jx-image {
width: 100%;
left: 0;
top: auto;
}
div.jx-image img {
height: 100%;
width: auto;
z-index: 5;
position: absolute;
margin-bottom: 0;
max-height: none;
max-width: none;
max-height: initial;
max-width: initial;
}
.vertical div.jx-image img {
height: auto;
width: 100%;
}
div.jx-image.jx-left {
left: 0;
background-position: left;
}
div.jx-image.jx-left img {
left: 0;
}
div.jx-image.jx-right {
right: 0;
background-position: right;
}
div.jx-image.jx-right img {
right: 0;
bottom: 0;
}
.veritcal div.jx-image.jx-left {
top: 0;
background-position: top;
}
.veritcal div.jx-image.jx-left img {
top: 0;
}
.vertical div.jx-image.jx-right {
bottom: 0;
background-position: bottom;
}
.veritcal div.jx-image.jx-right img {
bottom: 0;
}
div.jx-image div.jx-label {
font-size: 1em;
padding: .25em .75em;
position: relative;
display: inline-block;
top: 0;
background-color: #000; /* IE 8 */
background-color: rgba(0,0,0,.7);
color: white;
z-index: 10;
white-space: nowrap;
line-height: 18px;
vertical-align: middle;
}
div.jx-image.jx-left div.jx-label {
float: left;
left: 0;
}
div.jx-image.jx-right div.jx-label {
float: right;
right: 0;
}
.vertical div.jx-image div.jx-label {
display: table;
position: absolute;
}
.vertical div.jx-image.jx-right div.jx-label {
left: 0;
bottom: 0;
top: auto;
}
div.jx-credit {
line-height: 1.1;
font-size: 0.75em;
}
div.jx-credit em {
font-weight: bold;
font-style: normal;
}
/* Animation */
div.jx-image.transition {
transition: width .5s ease;
}
div.jx-handle.transition {
transition: left .5s ease;
}
.vertical div.jx-image.transition {
transition: height .5s ease;
}
.vertical div.jx-handle.transition {
transition: top .5s ease;
}
/* Knight Lab Credit */
a.jx-knightlab {
background-color: #000; /* IE 8 */
background-color: rgba(0,0,0,.25);
bottom: 0;
display: table;
height: 14px;
line-height: 14px;
padding: 1px 4px 1px 5px;
position: absolute;
right: 0;
text-decoration: none;
z-index: 10;
}
a.jx-knightlab div.knightlab-logo {
display: inline-block;
vertical-align: middle;
height: 8px;
width: 8px;
background-color: #c34528;
transform: rotate(45deg);
-ms-transform: rotate(45deg);
-webkit-transform: rotate(45deg);
top: -1.25px;
position: relative;
cursor: pointer;
}
a.jx-knightlab:hover {
background-color: #000; /* IE 8 */
background-color: rgba(0,0,0,.35);
}
a.jx-knightlab:hover div.knightlab-logo {
background-color: #ce4d28;
}
a.jx-knightlab span.juxtapose-name {
display: table-cell;
margin: 0;
padding: 0;
font-family: Helvetica, Arial, sans-serif;
font-weight: 300;
color: white;
font-size: 10px;
padding-left: 0.375em;
vertical-align: middle;
line-height: normal;
text-shadow: none;
}
/* keyboard accessibility */
div.jx-controller:focus,
div.jx-image.jx-left div.jx-label:focus,
div.jx-image.jx-right div.jx-label:focus,
a.jx-knightlab:focus {
background: #eae34a;
color: #000;
}
a.jx-knightlab:focus span.juxtapose-name{
color: #000;
border: none;
}
+24 -13
View File
@@ -8,9 +8,6 @@
letter-spacing: 0.01rem;
}
.jx-handle, .jx-arrow {
background-color: rgba(0,0,0,0);
}
/** COLOR VARIABLES */
:root {
@@ -26,13 +23,12 @@ header {
position: sticky;
top: 0;
z-index: 999;
border-bottom: 1px solid var(--gold);
}
.banner {
display: flex;
justify-content: space-between;
max-width: 90%;
width: 90%;
align-items: center;
height: 4.5rem;
@@ -93,8 +89,6 @@ section {
display: flex;
flex-direction: column;
align-items: center;
padding: 1rem 1rem;
}
/** TEXT TYPES */
@@ -166,7 +160,11 @@ footer {
/** DESKTOP RULES */
@media screen and (min-width: 851px) {
/** header */
header {
border-bottom: 1px solid var(--gold);
}
nav {
display: flex;
flex-direction: row;
@@ -185,31 +183,44 @@ footer {
@media screen and (max-width: 850px) {
/** header */
header {
padding: 0 1rem;
padding: 0;
width: 100%;
border-bottom: none;
/* background-color: blue; */
background-size: cover;
}
.banner {
/* background-color: orange; */
border-bottom: none;
}
nav {
/* background-color: red; */
display: flex;
flex-direction: column;
position: fixed;
gap: 2rem;
padding: 2rem 0;
padding: 4.8rem 0 4rem 0;
top: -20rem;
transition: top 0.5s ease;
top: -23rem;
left: 0;
width: 100%;
font-size: 2rem;
text-align: center;
border-bottom: 1px solid var(--gold);
transition: top 0.5s ease;
box-shadow: 0 10px 27px rgba(0, 0, 0, 0.05);
z-index: -1;
}
nav.active {
top: 1.5rem;
top: 2rem;
}
/** footer */
+764
View File
@@ -0,0 +1,764 @@
/* juxtapose - v1.2.2 - 2020-09-03
* Copyright (c) 2020 Alex Duner and Northwestern University Knight Lab
*/
/* juxtapose - v1.1.2 - 2015-07-16
* Copyright (c) 2015 Alex Duner and Northwestern University Knight Lab
*/
(function(document, window) {
var juxtapose = {
sliders: [],
OPTIMIZATION_ACCEPTED: 1,
OPTIMIZATION_WAS_CONSTRAINED: 2
};
var flickr_key = "d90fc2d1f4acc584e08b8eaea5bf4d6c";
var FLICKR_SIZE_PREFERENCES = ['Large', 'Medium'];
function Graphic(properties, slider) {
var self = this;
this.image = new Image();
this.loaded = false;
this.image.onload = function() {
self.loaded = true;
slider._onLoaded();
};
this.image.src = properties.src;
this.image.alt = properties.alt || '';
this.label = properties.label || false;
this.credit = properties.credit || false;
}
function FlickrGraphic(properties, slider) {
var self = this;
this.image = new Image();
this.loaded = false;
this.image.onload = function() {
self.loaded = true;
slider._onLoaded();
};
this.flickrID = this.getFlickrID(properties.src);
this.callFlickrAPI(this.flickrID, self);
this.label = properties.label || false;
this.credit = properties.credit || false;
}
FlickrGraphic.prototype = {
getFlickrID: function(url) {
if (url.match(/flic.kr\/.+/i)) {
var encoded = url.split('/').slice(-1)[0];
return base58Decode(encoded);
}
var idx = url.indexOf("flickr.com/photos/");
var pos = idx + "flickr.com/photos/".length;
var photo_info = url.substr(pos);
if (photo_info.indexOf('/') == -1) return null;
if (photo_info.indexOf('/') === 0) photo_info = photo_info.substr(1);
id = photo_info.split("/")[1];
return id;
},
callFlickrAPI: function(id, self) {
var url = 'https://api.flickr.com/services/rest/?method=flickr.photos.getSizes' +
'&api_key=' + flickr_key +
'&photo_id=' + id + '&format=json&nojsoncallback=1';
var request = new XMLHttpRequest();
request.open('GET', url, true);
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
data = JSON.parse(request.responseText);
var flickr_url = self.bestFlickrUrl(data.sizes.size);
self.setFlickrImage(flickr_url);
} else {
console.error("There was an error getting the picture from Flickr");
}
};
request.onerror = function() {
console.error("There was an error getting the picture from Flickr");
};
request.send();
},
setFlickrImage: function(src) {
this.image.src = src;
},
bestFlickrUrl: function(ary) {
var dict = {};
for (var i = 0; i < ary.length; i++) {
dict[ary[i].label] = ary[i].source;
}
for (var j = 0; j < FLICKR_SIZE_PREFERENCES.length; j++) {
if (FLICKR_SIZE_PREFERENCES[j] in dict) {
return dict[FLICKR_SIZE_PREFERENCES[j]];
}
}
return ary[0].source;
}
};
function getNaturalDimensions(DOMelement) {
if (DOMelement.naturalWidth && DOMelement.naturalHeight) {
return { width: DOMelement.naturalWidth, height: DOMelement.naturalHeight };
}
// https://www.jacklmoore.com/notes/naturalwidth-and-naturalheight-in-ie/
var img = new Image();
img.src = DOMelement.src;
return { width: img.width, height: img.height };
}
function getImageDimensions(img) {
var dimensions = {
width: getNaturalDimensions(img).width,
height: getNaturalDimensions(img).height,
aspect: function() { return (this.width / this.height); }
};
return dimensions;
}
function addClass(element, c) {
if (element.classList) {
element.classList.add(c);
} else {
element.className += " " + c;
}
}
function removeClass(element, c) {
element.className = element.className.replace(/(\S+)\s*/g, function(w, match) {
if (match === c) {
return '';
}
return w;
}).replace(/^\s+/, '');
}
function setText(element, text) {
if (document.body.textContent) {
element.textContent = text;
} else {
element.innerText = text;
}
}
function getComputedWidthAndHeight(element) {
if (window.getComputedStyle) {
// path taken on mac
return {
width: parseInt(getComputedStyle(element).width, 10),
height: parseInt(getComputedStyle(element).height, 10)
};
} else {
w = element.getBoundingClientRect().right - element.getBoundingClientRect().left;
h = element.getBoundingClientRect().bottom - element.getBoundingClientRect().top;
return {
width: parseInt(w, 10) || 0,
height: parseInt(h, 10) || 0
};
}
}
function viewport() {
var e = window,
a = 'inner';
if (!('innerWidth' in window)) {
a = 'client';
e = document.documentElement || document.body;
}
return { width: e[a + 'Width'], height: e[a + 'Height'] }
}
function getPageX(e) {
var pageX;
if (e.pageX) {
pageX = e.pageX;
} else if (e.touches) {
pageX = e.touches[0].pageX;
} else {
pageX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
}
return pageX;
}
function getPageY(e) {
var pageY;
if (e.pageY) {
pageY = e.pageY;
} else if (e.touches) {
pageY = e.touches[0].pageY;
} else {
pageY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
return pageY;
}
function checkFlickr(url) {
if (url.match(/flic.kr\/.+/i)) {
return true;
}
var idx = url.indexOf("flickr.com/photos/");
if (idx == -1) {
return false;
} else {
return true;
}
}
function base58Decode(encoded) {
var alphabet = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ',
base = alphabet.length;
if (typeof encoded !== 'string') {
throw '"base58Decode" only accepts strings.';
}
var decoded = 0;
while (encoded) {
var alphabetPosition = alphabet.indexOf(encoded[0]);
if (alphabetPosition < 0) {
throw '"base58Decode" can\'t find "' + encoded[0] + '" in the alphabet: "' + alphabet + '"';
}
var powerOf = encoded.length - 1;
decoded += alphabetPosition * (Math.pow(base, powerOf));
encoded = encoded.substring(1);
}
return decoded.toString();
}
function getLeftPercent(slider, input) {
if (typeof(input) === "string" || typeof(input) === "number") {
leftPercent = parseInt(input, 10);
} else {
var sliderRect = slider.getBoundingClientRect();
var offset = {
top: sliderRect.top + document.body.scrollTop + document.documentElement.scrollTop,
left: sliderRect.left + document.body.scrollLeft + document.documentElement.scrollLeft
};
var width = slider.offsetWidth;
var pageX = getPageX(input);
var relativeX = pageX - offset.left;
leftPercent = (relativeX / width) * 100;
}
return leftPercent;
}
function getTopPercent(slider, input) {
if (typeof(input) === "string" || typeof(input) === "number") {
topPercent = parseInt(input, 10);
} else {
var sliderRect = slider.getBoundingClientRect();
var offset = {
top: sliderRect.top + document.body.scrollTop + document.documentElement.scrollTop,
left: sliderRect.left + document.body.scrollLeft + document.documentElement.scrollLeft
};
var width = slider.offsetHeight;
var pageY = getPageY(input);
var relativeY = pageY - offset.top;
topPercent = (relativeY / width) * 100;
}
return topPercent;
}
// values of BOOLEAN_OPTIONS are ignored. just used for 'in' test on keys
var BOOLEAN_OPTIONS = { 'animate': true, 'showLabels': true, 'showCredits': true, 'makeResponsive': true };
function interpret_boolean(x) {
if (typeof(x) != 'string') {
return Boolean(x);
}
return !(x === 'false' || x === '');
}
function JXSlider(selector, images, options) {
this.selector = selector;
var i;
this.options = { // new options must have default values set here.
animate: true,
showLabels: true,
showCredits: true,
makeResponsive: true,
startingPosition: "50%",
mode: 'horizontal',
callback: null // pass a callback function if you like
};
for (i in this.options) {
if (i in options) {
if (i in BOOLEAN_OPTIONS) {
this.options[i] = interpret_boolean(options[i]);
} else {
this.options[i] = options[i];
}
}
}
if (images.length == 2) {
if (checkFlickr(images[0].src)) {
this.imgBefore = new FlickrGraphic(images[0], this);
} else {
this.imgBefore = new Graphic(images[0], this);
}
if (checkFlickr(images[1].src)) {
this.imgAfter = new FlickrGraphic(images[1], this);
} else {
this.imgAfter = new Graphic(images[1], this);
}
} else {
console.warn("The images parameter takes two Image objects.");
}
if (this.imgBefore.credit || this.imgAfter.credit) {
this.options.showCredits = true;
} else {
this.options.showCredits = false;
}
}
JXSlider.prototype = {
updateSlider: function(input, animate) {
var leftPercent, rightPercent;
if (this.options.mode === "vertical") {
leftPercent = getTopPercent(this.slider, input);
} else {
leftPercent = getLeftPercent(this.slider, input);
}
leftPercent = leftPercent.toFixed(2) + "%";
leftPercentNum = parseFloat(leftPercent);
rightPercent = (100 - leftPercentNum) + "%";
if (leftPercentNum > 0 && leftPercentNum < 100) {
removeClass(this.handle, 'transition');
removeClass(this.rightImage, 'transition');
removeClass(this.leftImage, 'transition');
if (this.options.animate && animate) {
addClass(this.handle, 'transition');
addClass(this.leftImage, 'transition');
addClass(this.rightImage, 'transition');
}
if (this.options.mode === "vertical") {
this.handle.style.top = leftPercent;
this.leftImage.style.height = leftPercent;
this.rightImage.style.height = rightPercent;
} else {
this.handle.style.left = leftPercent;
this.leftImage.style.width = leftPercent;
this.rightImage.style.width = rightPercent;
}
this.sliderPosition = leftPercent;
}
},
getPosition: function() {
return this.sliderPosition;
},
displayLabel: function(element, labelText) {
label = document.createElement("div");
label.className = 'jx-label';
label.setAttribute('tabindex', 0); //put the controller in the natural tab order of the document
setText(label, labelText);
element.appendChild(label);
},
displayCredits: function() {
credit = document.createElement("div");
credit.className = "jx-credit";
text = "<em>Photo Credits:</em>";
if (this.imgBefore.credit) { text += " <em>Before</em> " + this.imgBefore.credit; }
if (this.imgAfter.credit) { text += " <em>After</em> " + this.imgAfter.credit; }
credit.innerHTML = text;
this.wrapper.appendChild(credit);
},
setStartingPosition: function(s) {
this.options.startingPosition = s;
},
checkImages: function() {
if (getImageDimensions(this.imgBefore.image).aspect() ==
getImageDimensions(this.imgAfter.image).aspect()) {
return true;
} else {
return false;
}
},
calculateDims: function(width, height) {
var ratio = getImageDimensions(this.imgBefore.image).aspect();
if (width) {
height = width / ratio;
} else if (height) {
width = height * ratio;
}
return {
width: width,
height: height,
ratio: ratio
};
},
responsivizeIframe: function(dims) {
//Check the slider dimensions against the iframe (window) dimensions
if (dims.height < window.innerHeight) {
//If the aspect ratio is greater than 1, imgs are landscape, so letterbox top and bottom
if (dims.ratio >= 1) {
this.wrapper.style.paddingTop = parseInt((window.innerHeight - dims.height) / 2) + "px";
}
} else if (dims.height > window.innerHeight) {
/* If the image is too tall for the window, which happens at 100% width on large screens,
* force dimension recalculation based on height instead of width */
dims = this.calculateDims(0, window.innerHeight);
this.wrapper.style.paddingLeft = parseInt((window.innerWidth - dims.width) / 2) + "px";
}
if (this.options.showCredits) {
// accommodate the credits box within the iframe
dims.height -= 13;
}
return dims;
},
setWrapperDimensions: function() {
var wrapperWidth = getComputedWidthAndHeight(this.wrapper).width;
var wrapperHeight = getComputedWidthAndHeight(this.wrapper).height;
var dims = this.calculateDims(wrapperWidth, wrapperHeight);
// if window is in iframe, make sure images don't overflow boundaries
if (window.location !== window.parent.location && !this.options.makeResponsive) {
dims = this.responsivizeIframe(dims);
}
this.wrapper.style.height = parseInt(dims.height) + "px";
this.wrapper.style.width = parseInt(dims.width) + "px";
},
optimizeWrapper: function(maxWidth) {
var result = juxtapose.OPTIMIZATION_ACCEPTED;
if ((this.imgBefore.image.naturalWidth >= maxWidth) && (this.imgAfter.image.naturalWidth >= maxWidth)) {
this.wrapper.style.width = maxWidth + "px";
result = juxtapose.OPTIMIZATION_WAS_CONSTRAINED;
} else if (this.imgAfter.image.naturalWidth < maxWidth) {
this.wrapper.style.width = this.imgAfter.image.naturalWidth + "px";
} else {
this.wrapper.style.width = this.imgBefore.image.naturalWidth + "px";
}
this.setWrapperDimensions();
return result;
},
_onLoaded: function() {
if (this.imgBefore && this.imgBefore.loaded === true &&
this.imgAfter && this.imgAfter.loaded === true) {
this.wrapper = document.querySelector(this.selector);
addClass(this.wrapper, 'juxtapose');
this.wrapper.style.width = getNaturalDimensions(this.imgBefore.image).width;
this.setWrapperDimensions();
this.slider = document.createElement("div");
this.slider.className = 'jx-slider';
this.wrapper.appendChild(this.slider);
if (this.options.mode != "horizontal") {
addClass(this.slider, this.options.mode);
}
this.handle = document.createElement("div");
this.handle.className = 'jx-handle';
this.rightImage = document.createElement("div");
this.rightImage.className = 'jx-image jx-right';
this.rightImage.appendChild(this.imgAfter.image);
this.leftImage = document.createElement("div");
this.leftImage.className = 'jx-image jx-left';
this.leftImage.appendChild(this.imgBefore.image);
this.labCredit = document.createElement("a");
this.labCredit.setAttribute('href', 'https://juxtapose.knightlab.com');
this.labCredit.setAttribute('target', '_blank');
this.labCredit.setAttribute('rel', 'noopener');
this.labCredit.className = 'jx-knightlab';
this.labLogo = document.createElement("div");
this.labLogo.className = 'knightlab-logo';
this.labCredit.appendChild(this.labLogo);
this.projectName = document.createElement("span");
this.projectName.className = 'juxtapose-name';
setText(this.projectName, 'JuxtaposeJS');
this.labCredit.appendChild(this.projectName);
this.slider.appendChild(this.handle);
this.slider.appendChild(this.leftImage);
this.slider.appendChild(this.rightImage);
this.slider.appendChild(this.labCredit);
this.leftArrow = document.createElement("div");
this.rightArrow = document.createElement("div");
this.control = document.createElement("div");
this.controller = document.createElement("div");
this.leftArrow.className = 'jx-arrow jx-left';
this.rightArrow.className = 'jx-arrow jx-right';
this.control.className = 'jx-control';
this.controller.className = 'jx-controller';
this.controller.setAttribute('tabindex', 0); //put the controller in the natural tab order of the document
this.controller.setAttribute('role', 'slider');
this.controller.setAttribute('aria-valuenow', 50);
this.controller.setAttribute('aria-valuemin', 0);
this.controller.setAttribute('aria-valuemax', 100);
this.handle.appendChild(this.leftArrow);
this.handle.appendChild(this.control);
this.handle.appendChild(this.rightArrow);
this.control.appendChild(this.controller);
this._init();
}
},
_init: function() {
if (this.checkImages() === false) {
console.warn(this, "Check that the two images have the same aspect ratio for the slider to work correctly.");
}
this.updateSlider(this.options.startingPosition, false);
if (this.options.showLabels === true) {
if (this.imgBefore.label) { this.displayLabel(this.leftImage, this.imgBefore.label); }
if (this.imgAfter.label) { this.displayLabel(this.rightImage, this.imgAfter.label); }
}
if (this.options.showCredits === true) {
this.displayCredits();
}
var self = this;
// SIMON REMOVED
// window.addEventListener("resize", function() {
// self.setWrapperDimensions();
// });
// Set up Javascript Events
// On mousedown, call updateSlider then set animate to false
// (if animate is true, adds css transition when updating).
this.slider.addEventListener("mousedown", function(e) {
e = e || window.event;
e.preventDefault();
self.updateSlider(e, true);
animate = true;
this.addEventListener("mousemove", function(e) {
e = e || window.event;
e.preventDefault();
if (animate) { self.updateSlider(e, false); }
});
this.addEventListener('mouseup', function(e) {
e = e || window.event;
e.preventDefault();
e.stopPropagation();
this.removeEventListener('mouseup', arguments.callee);
animate = false;
});
});
this.slider.addEventListener("touchstart", function(e) {
e = e || window.event;
// e.preventDefault();
// e.stopPropagation();
self.updateSlider(e, true);
this.addEventListener("touchmove", function(e) {
e = e || window.event;
// e.preventDefault();
// e.stopPropagation();
self.updateSlider(event, false);
});
});
/* keyboard accessibility */
this.handle.addEventListener("keydown", function(e) {
e = e || window.event;
var key = e.which || e.keyCode;
var ariaValue = parseFloat(this.style.left);
//move jx-controller left
if (key == 37) {
ariaValue = ariaValue - 1;
var leftStart = parseFloat(this.style.left) - 1;
self.updateSlider(leftStart, false);
self.controller.setAttribute('aria-valuenow', ariaValue);
}
//move jx-controller right
if (key == 39) {
ariaValue = ariaValue + 1;
var rightStart = parseFloat(this.style.left) + 1;
self.updateSlider(rightStart, false);
self.controller.setAttribute('aria-valuenow', ariaValue);
}
});
//toggle right-hand image visibility
this.leftImage.addEventListener("keydown", function(event) {
var key = event.which || event.keyCode;
if ((key == 13) || (key == 32)) {
self.updateSlider("90%", true);
self.controller.setAttribute('aria-valuenow', 90);
}
});
//toggle left-hand image visibility
this.rightImage.addEventListener("keydown", function(event) {
var key = event.which || event.keyCode;
if ((key == 13) || (key == 32)) {
self.updateSlider("10%", true);
self.controller.setAttribute('aria-valuenow', 10);
}
});
juxtapose.sliders.push(this);
if (this.options.callback && typeof(this.options.callback) == 'function') {
this.options.callback(this);
}
}
};
/*
Given an element that is configured with the proper data elements, make a slider out of it.
Normally this will just be used by scanPage.
*/
juxtapose.makeSlider = function(element, idx) {
if (typeof idx == 'undefined') {
idx = juxtapose.sliders.length; // not super threadsafe...
}
var w = element;
var images = w.querySelectorAll('img');
var options = {};
// don't set empty string into options, that's a false false.
if (w.getAttribute('data-animate')) {
options.animate = w.getAttribute('data-animate');
}
if (w.getAttribute('data-showlabels')) {
options.showLabels = w.getAttribute('data-showlabels');
}
if (w.getAttribute('data-showcredits')) {
options.showCredits = w.getAttribute('data-showcredits');
}
if (w.getAttribute('data-startingposition')) {
options.startingPosition = w.getAttribute('data-startingposition');
}
if (w.getAttribute('data-mode')) {
options.mode = w.getAttribute('data-mode');
}
if (w.getAttribute('data-makeresponsive')) {
options.mode = w.getAttribute('data-makeresponsive');
}
specificClass = 'juxtapose-' + idx;
addClass(element, specificClass);
selector = '.' + specificClass;
if (w.innerHTML) {
w.innerHTML = '';
} else {
w.innerText = '';
}
slider = new juxtapose.JXSlider(
selector, [{
src: images[0].src,
label: images[0].getAttribute('data-label'),
credit: images[0].getAttribute('data-credit'),
alt: images[0].alt
},
{
src: images[1].src,
label: images[1].getAttribute('data-label'),
credit: images[1].getAttribute('data-credit'),
alt: images[1].alt
}
],
options
);
};
//Enable HTML Implementation
juxtapose.scanPage = function() {
var elements = document.querySelectorAll('.juxtapose');
for (var i = 0; i < elements.length; i++) {
juxtapose.makeSlider(elements[i], i);
}
};
juxtapose.JXSlider = JXSlider;
window.juxtapose = juxtapose;
juxtapose.scanPage();
}(document, window));
// addEventListener polyfill / jonathantneal
!window.addEventListener && (function(WindowPrototype, DocumentPrototype, ElementPrototype, addEventListener, removeEventListener, dispatchEvent, registry) {
WindowPrototype[addEventListener] = DocumentPrototype[addEventListener] = ElementPrototype[addEventListener] = function(type, listener) {
var target = this;
registry.unshift([target, type, listener, function(event) {
event.currentTarget = target;
event.preventDefault = function() { event.returnValue = false };
event.stopPropagation = function() { event.cancelBubble = true };
event.target = event.srcElement || target;
listener.call(target, event);
}]);
this.attachEvent("on" + type, registry[0][3]);
};
WindowPrototype[removeEventListener] = DocumentPrototype[removeEventListener] = ElementPrototype[removeEventListener] = function(type, listener) {
for (var index = 0, register; register = registry[index]; ++index) {
if (register[0] == this && register[1] == type && register[2] == listener) {
return this.detachEvent("on" + type, registry.splice(index, 1)[0][3]);
}
}
};
WindowPrototype[dispatchEvent] = DocumentPrototype[dispatchEvent] = ElementPrototype[dispatchEvent] = function(eventObject) {
return this.fireEvent("on" + eventObject.type, eventObject);
};
})(Window.prototype, HTMLDocument.prototype, Element.prototype, "addEventListener", "removeEventListener", "dispatchEvent", []);
+146 -132
View File
@@ -9,38 +9,51 @@
<title>Luxury Detailing Maine</title>
<link href="css/style.css" rel="stylesheet" />
<link href="css/hamburger.css" rel="stylesheet" />
<!-- IMAGE SLIDER CSS IMPORT -->
<link rel="stylesheet" href="https://cdn.knightlab.com/libs/juxtapose/latest/css/juxtapose.css">
<!-- juxtapose is the image slider / comparison tool-->
<link href="css/juxtapose.css" rel="stylesheet" />
<style>
main {
margin-top: 5rem;
h1 {
margin-bottom: 5rem;
}
.jx-handle, .jx-arrow {
background-color: rgba(0,0,0,0);
}
/** REVIEWS */
#reviews {
margin-top: 7vh;
}
#reviewContainer {
display: flex;
flex-wrap: wrap;
gap: 3rem;
gap: 4rem;
width: 90%;
align-items: center;
justify-content: center;
justify-content: space-evenly;
}
/** REVIEW CARD */
.review-card {
padding: 1rem;
display: flex;
flex-direction: column;
justify-content: flex-start;
padding: 3rem;
border: 2px solid var(--gold);
border-radius: 1rem;
transition: 0.5s ease;
transition: 0.3s ease;
color: white;
}
.review-card:hover {
border: 2px solid white;
box-shadow: 0px 0px 10px 1px var(--gold);
}
.review-card-head {
@@ -52,31 +65,41 @@
color: var(--gold);
}
.review-card-head a {
color: white;
color: var(--gold);
}
.review-card-head a:hover {
color: var(--gold);
color: white;
}
.review-card-head-left {
word-wrap: none;
text-align: left;
}
.review-card-head-right {
word-wrap: none;
text-align: right;
}
.review-card-body {
max-height: 15rem;
overflow-y: auto;
overflow: hidden;
text-align: left;
color: white;
align-self: center;
}
/** GALLERY */
#gallery {
min-width: 100%;
}
#galleryPhotos {
#imageContainer {
display: flex;
min-width: 75%;
width: 90%;
flex-direction: column;
align-items: center;
}
/** JUXTAPOSE STUFF **/
/** JUXTAPOSE ADJUSTMENTS **/
a.jx-knightlab div.knightlab-logo {
visibility: hidden !important;
display: none !important;
@@ -99,60 +122,8 @@
div.jx-arrow.jx-right {
border-color: transparent transparent transparent var(--gold);
}
/** BEFORE / AFTER IMAGES */
.before-after-pair {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 75%;
}
.before-img-container {
max-height: 100%;
}
.after-img-container {
position: absolute;
top: 0;
max-height: 100%;
opacity: 0%;
transition: .5s;
}
.after-img-container:hover {
opacity: 100%;
}
.after-img {
height: 100%;
width: 100%;
}
.img-state-text {
position: absolute;
top: 0;
left: 0;
font-size: .8rem;
background-color: rgba(0, 0, 0, 0.8);
padding: .25rem 1rem;
}
.before-img-container .img-state-text {
color: white;
}
.after-img-container .img-state-text {
color: var(--gold);
}
/**
/************************************************
* DESKTOP RULES
*/
@media screen and (min-width: 851px) {
@@ -161,23 +132,18 @@
}
.review-card {
min-width: 40%;
max-width: 45%;
overflow: hidden;
width: 25rem;
height: min-content;
}
#galleryPhotos {
#imageContainer {
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: center;
justify-content: space-between;
gap: 2rem;
}
.before-after-pair, .before-img, .after-img-container {
width: 400px;
}
}
/*
@@ -186,22 +152,18 @@
@media screen and (max-width: 850px) {
#reviewContainer {
flex-direction: column;
width: 100%;
}
#galleryPhotos {
#imageContainer {
flex-direction: column;
gap: 2rem;
}
.review-card {
width: 100%;
width: 90%;
}
.before-after-pair, .before-img, .after-img-container {
width: 100%;
}
}
</style>
@@ -234,45 +196,26 @@
</header>
<main id="main-content">
<section>
<h1 style="margin-bottom: 2rem">Reviews</h1>
<section id="reviews">
<h1>Reviews</h1>
<div id="reviewContainer"></div>
</section>
<section id="gallery">
<h1 style="margin-bottom: 2rem">Gallery</h1>
<div id="galleryPhotos">
<h1>Before & After Gallery</h1>
<div id="imageContainer">
<div class="juxtapose" style="width: 45%;">
<div class="juxtapose">
<img src="assets/img/before1.jpg" alt="An interior of a car trunk, the floor is covered in dirt and grass." />
<img src="assets/img/after1.jpg" alt="The same trunk after it has been restored to like-new condition." />
</div>
<div class="juxtapose" style="width: 45%;">
<div class="juxtapose">
<img src="assets/img/before1.jpg" alt="An interior of a car trunk, the floor is covered in dirt and grass." />
<img src="assets/img/after1.jpg" alt="The same trunk after it has been restored to like-new condition." />
</div>
<!-- KEPT AS REMINDER -->
<!--
<figure class="before-after-pair">
<span class="before-img-container">
<figcaption class="img-state-text">before</figcaption>
<img class="before-img" src="assets/img/before1.jpg"
alt="An interior of a car trunk, the floor is covered in dirt and grass.">
</img>
</span>
<span class="after-img-container">
<figcaption class="img-state-text">after</figcaption>
<img class="after-img" src="assets/img/after1.jpg"
alt="The same trunk after it has been restored to like-new condition."></img>
</span>
</figure>
-->
</div>
</section>
</main>
@@ -294,33 +237,84 @@
</div>
</footer>
<script src="js/hamburger.js"></script>
<script src="js/juxtapose.js"></script>
<script>
/** script stuff just for this page */
// on page init
window.onload = function () {
insertReviews(reviewData);
window.addEventListener("scroll", jiggleGallery);
window.addEventListener("resize", manageImageSize);
manageImageSize();
};
function manageImageSize() {
let isDesktop = window.innerWidth > 850;
let newWidth, newHeight;
if (isDesktop) {
newWidth = window.innerWidth * 0.40;
newHeight = newWidth * 1.2;
}
else {
newWidth = window.innerWidth * 0.80;
newHeight = newWidth * 1.2;
}
// resize iframes
let images = document.getElementsByClassName("juxtapose");
for (let i = 0; i < images.length; i++) {
images[i].style.width = newWidth + 'px';
images[i].style.height = newHeight + 'px';
}
// resize images in iframe to match
images = document.getElementsByClassName("jx-image");
for (let i = 0; i < images.length; i++) {
images[i].style.width = newWidth + 'px';
images[i].style.height = newHeight + 'px';
images[i].children[0].style.width = newWidth + 'px';
images[i].children[0].style.height = newHeight + 'px';
}
}
/** review insertion **/
let reviewData = [
{
name: "Adrianna Weber",
text: "Incredible Transformation! ... Luxury Detailing exceeded expectations, revitalizing both of our vehicles and providing a truly impressive level of service. Highly recommended!",
url: "https://share.google/HA8xBsNKPDGCdqYSh",
name: "Sasha M.",
text: "Easy 5 stars! Aden was super communicative, very friendly, and reasonably priced. I would happily recommend this service to anyone.",
url: "https://share.google/zUOkEQ8Yv6L2SkBsl",
},
{
name: "Tara Dossiema",
text: "Job well done, had the vehicle feeling like it just left the lot. You can tell Aden takes great pride and is meticulous in his work. Great communication and the attention to detail to all aspects, deserving of 5 stars+!",
name: "Tara D.",
text: "Job well done, had the vehicle feeling like it just left the lot. You can tell Aden takes great pride and is meticulous in his work.",
url: "https://share.google/uMS4Ma8xT7ARAqf83",
},
{
name: "Sasha Mackey",
text: "Easy 5 stars! Aden was super communicative, very friendly, and reasonably priced. He comes to you, which is super convenient! I got a car detailed as a birthday gift and it was so fun to present. No complaints! I would happily recommend this service to anyone. Thanks, Aden!",
url: "https://share.google/zUOkEQ8Yv6L2SkBsl",
name: "Adrianna W.",
text: "Luxury Detailing's Platinum package delivered incredible results, making my partners 15-year-old car look and feel brand new! ",
url: "https://maps.app.goo.gl/vS8JALPh7KNquRyu7",
},
{
name: "TEST",
text: "Easy 5 stars! Aden was super communicative, very friendly, and reasonably priced. He comes to you, which is super convenient! I got a car detailed as a birthday gift and it was so fun to present. No complaints! I would happily recommend this service to anyone. Thanks, Aden!Easy 5 stars! Aden was super communicative, very friendly, and reasonably priced. He comes to you, which is super convenient! I got a car detailed as a birthday gift and it was so fun to present. No complaints! I would happily recommend this service to anyone. Thanks, AdenEasy 5 stars! Aden was super communicative, very friendly, and reasonably priced. He comes to you, which is super convenient! I got a car detailed as a birthday gift and it was so fun to present. No complaints! I would happily recommend this service to anyone. Thanks, AdenEasy 5 stars! Aden was super communicative, very friendly, and reasonably priced. He comes to you, which is super convenient! I got a car detailed as a birthday gift and it was so fun to present. No complaints! I would happily recommend this service to anyone. Thanks, AdenEasy 5 stars! Aden was super communicative, very friendly, and reasonably priced. He comes to you, which is super convenient! I got a car detailed as a birthday gift and it was so fun to present. No complaints! I would happily recommend this service to anyone. Thanks, AdenEasy 5 stars! Aden was super communicative, very friendly, and reasonably priced. He comes to you, which is super convenient! I got a car detailed as a birthday gift and it was so fun to present. No complaints! I would happily recommend this service to anyone. Thanks, Aden",
url: "https://share.google/zUOkEQ8Yv6L2SkBsl",
name: "Adam B.",
text: "Aden was easy to work with and did a fantastic job! Im very happy with how the detailing came out! Great pricing for high quality service!",
url: "https://maps.app.goo.gl/Hp9Ez5hEF9iTrbn6A"
},
{
name: "Jacob S.",
text: "I could not be happier with the way my Cadillac turned out. The way the car came out is a night and day difference.",
url: "https://share.google/89ldWBKVGn25MnHPM"
},
{
name: "Sasha P.",
text: "I second all of the great reviews so far, Aden really is that great! My car really does look practically brand new.",
url: "https://share.google/ax2m0N4UOjvWIJpo0",
},
];
function insertReviews(reviews) {
let target = document.getElementById("reviewContainer");
console.log(reviewData);
for (let i = 0; i < reviews.length; i++) {
let review = reviews[i];
let linkedName = `<a href="${review.url}" class="reviewLink" target="_blank">${review.name}</a>`;
@@ -334,7 +328,7 @@
<span class="review-card-head-left">
${review.url ? linkedName : review.name}
</span>
<span class="review-card-head-left">
<span class="review-card-head-right">
<span>★</span>
<span>★</span>
<span>★</span>
@@ -342,22 +336,42 @@
<span>★</span>
</span>
</div>
<div class="review-card-body">
<p>${review.text}</p>
</div>
<p class="review-card-body">
${review.text}
</p>
</div>
`
);
}
}
// upon page load
window.onload = function () {
insertReviews(reviewData);
};
/** gallery jiggling (indicate to user they can use slider) */
let jiggled = false;
async function jiggleGallery() {
if (window.scrollY < 850 || jiggled) return;
// once user scrolls to gallery, change sliders on all images
let sliders = juxtapose.sliders;
for (let i = 0; i < sliders.length; i++) {
sliders[i].updateSlider(65, true);
}
// sleep, then slide again
await new Promise(resolve => setTimeout(resolve, 1000));
for (let i = 0; i < sliders.length; i++) {
sliders[i].updateSlider(35, true);
}
// sleep, then slide back to center
await new Promise(resolve => setTimeout(resolve, 1000));
for (let i = 0; i < sliders.length; i++) {
sliders[i].updateSlider(50, true);
}
jiggled = true;
}
</script>
<script src="https://cdn.knightlab.com/libs/juxtapose/latest/js/juxtapose.min.js"></script>
<!-- <script src="https://cdn.knightlab.com/libs/juxtapose/latest/js/juxtapose.min.js"></script> -->
</body>
</html>
+97 -61
View File
@@ -13,11 +13,15 @@
<link href="css/hamburger.css" rel="stylesheet" />
<style>
html { scroll-behavior: smooth; }
main {
gap: 12rem;
}
/** landings are used to jump slightly above categories */
.landing { top: -5rem; position: relative; }
#categoryLinks {
display: flex;
justify-content: space-evenly;
@@ -27,16 +31,26 @@
margin-top: 2rem;
}
#servicesContainer {
#categoryContainer {
width: 90%;
height: 100%;
}
#tableOfContents {
width: 90%;
margin-top: 5rem;
.serviceContainer {
display: flex;
align-items: center;
justify-content: space-evenly;
margin: 3rem 0;
}
#tableOfContents {
width: 90%;
margin-top: 35vh;
margin-bottom: 25vh;
}
/** Service Card */
.card {
padding: 3rem;
border: 2px solid var(--gold);
@@ -44,7 +58,7 @@
transition: 0.5s ease;
color: white;
width: 25%;
width: 30%;
}
.card:hover {
border: 2px solid white;
@@ -70,6 +84,8 @@
}
/** Booking Button */
.book-button {
background-color: var(--black);
color: var(--gold);
@@ -144,44 +160,41 @@
<main id="main-content">
<div id="tableOfContents">
<h1>Service Categories</h1>
<h1>Our Services</h1>
<div id="categoryLinks">
<h2><a href="#packages">Service Packages</a></h2>
<h2><a href="#ceramic">Ceramic Coating</a></h2>
<h2><a href="#paint">Polish & Paint Correction</a></h2>
<h2><a href="#exterior">Exterior Detailing</a></h2>
<h2><a href="#interior">Interior Detailing</a></h2>
<h2><a href="#packagesJump">Service Packages</a></h2>
<h2><a href="#ceramicJump">Ceramic Coating</a></h2>
<h2><a href="#paintJump">Polish & Paint Correction</a></h2>
<h2><a href="#exteriorJump">Exterior Detailing</a></h2>
<h2><a href="#interiorJump">Interior Detailing</a></h2>
</div>
</div>
<div id="servicesContainer">
<div id="categoryContainer">
<div class="card" >
<div class="card-head">
<h2>Service Package One</h2>
</div>
<div class="card-body">
<span>
<h3>Interior</h3>
<ul>
<li>Vacuum & light carpet shampooing</li>
<li>Plastics, leather, and vinyls cleaning</li>
<li>Light seat shampooing</li>
<li>Interior & exterior window cleaning</li>
<li>Crack & crevice cleaning</li>
</ul>
</span>
<span>
<h3>Exterior</h3>
<ul>
<li>Hand wash & dry</li>
<li>Bug gut removal</li>
<li>Tire, wheel, and wheel well cleaning + tire shine</li>
<li>Window cleaning</li>
<li>Light door jam cleaning</li>
</ul>
</span>
</div>
<span id="packagesJump" class="landing"></span>
<h1>Service Packages</h1>
<div id="packages" class="serviceContainer">
</div>
<span id="ceramicJump" class="landing"></span>
<h1>Ceramic Coating</h1>
<div id="ceramic" class="serviceContainer">
</div>
<span id="paintJump" class="landing"></span>
<h1>Paint & Polish Correction</h1>
<div id="paint" class="serviceContainer">
</div>
<span id="exteriorJump" class="landing"></span>
<h1>Exterior Detailing</h1>
<div id="exterior" class="serviceContainer">
</div>
<span id="interiorJump" class="landing"></span>
<h1>Interior Detailing</h1>
<div id="interior" class="serviceContainer">
</div>
</div>
@@ -207,31 +220,54 @@
</footer>
<script src="js/hamburger.js"></script>
<script>
function toggleDropdown(headerClicked) {
let dropdown = headerClicked.parentElement;
let body = headerClicked.nextElementSibling;
let arrow = headerClicked.children[0];
if (dropdown.classList.contains("longest")) {
dropdown.classList.toggle("open-longest");
body.classList.toggle("open-longest");
arrow.classList.toggle("open");
}
else if (dropdown.classList.contains("long")) {
dropdown.classList.toggle("open-long");
body.classList.toggle("open-long");
arrow.classList.toggle("open");
}
else {
dropdown.classList.toggle("open");
body.classList.toggle("open");
arrow.classList.toggle("open");
}
let isOpen = dropdown.className.includes("open");
dropdown.setAttribute('aria-expanded', isOpen);
// these categories must match the IDs of the serviceContainer <div>s above
const categories = ["packages", "ceramic", "paint", "exterior", "interior"];
const serviceData = {};
function insertServices(serviceData) {
for (const category of categories) {
console.log(category);
let target = document.getElementById(category);
target.insertAdjacentHTML(
"beforeend",
`
<div class="card" >
<div class="card-head">
<h2>Service Package One</h2>
</div>
<div class="card-body">
<span>
<h3>Interior</h3>
<ul>
<li>Vacuum & light carpet shampooing</li>
<li>Plastics, leather, and vinyls cleaning</li>
<li>Light seat shampooing</li>
<li>Interior & exterior window cleaning</li>
<li>Crack & crevice cleaning</li>
</ul>
</span>
<span>
<h3>Exterior</h3>
<ul>
<li>Hand wash & dry</li>
<li>Bug gut removal</li>
<li>Tire, wheel, and wheel well cleaning + tire shine</li>
<li>Window cleaning</li>
<li>Light door jam cleaning</li>
</ul>
</span>
</div>
</div>
`);
}
}
// insert services upon page load
window.onload = (event) => {
insertServices(serviceData);
}
</script>
</body>
</html>