Move scripts and stylesheets into /assets
To keep things organised put the assets supporting pages into sub-folders in /assets.
This commit is contained in:
parent
fed75e4930
commit
07a7386e1d
3 changed files with 2 additions and 2 deletions
130
web/assets/scripts/viewport.js
Normal file
130
web/assets/scripts/viewport.js
Normal file
|
@ -0,0 +1,130 @@
|
|||
let heightOffset = 0;
|
||||
let lastHeight = window.innerHeight;
|
||||
function getWindowHeight(event) {
|
||||
|
||||
/*
|
||||
When scrolling such that the browser toolbar is pushed out of view or
|
||||
pulled into view on a mobile device the height of the viewport changes.
|
||||
This causes the perspective to shift noticeably when you let go of your
|
||||
finger after scrolling. To avoid this, I store the last change of the
|
||||
window height if this was previously 0 and apply that to the calculated
|
||||
height of the window when the size changes.
|
||||
|
||||
This causes the heightOffset to toggle between 0 and the value that
|
||||
negates the perspective shift when you switch between having the browser
|
||||
toolbar visible and not visible on mobile, but stay as 0 in most other
|
||||
situations involving window resize.
|
||||
*/
|
||||
if (event?.type === 'resize') {
|
||||
if (heightOffset === 0 && lastHeight !== 0) {
|
||||
const newOffset = lastHeight - window.innerHeight;
|
||||
// Expect the browser toolbar resize to be between 20 and 100 pixels.
|
||||
if (Math.abs(newOffset) > 20 && Math.abs(newOffset) < 100) {
|
||||
heightOffset = newOffset;
|
||||
} else {
|
||||
heightOffset = 0;
|
||||
}
|
||||
} else {
|
||||
heightOffset = 0;
|
||||
}
|
||||
lastHeight = window.innerHeight;
|
||||
}
|
||||
return window.innerHeight + heightOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
Returns x rescaled such that the values in the range a-b are mapped to the range c-d.
|
||||
*/
|
||||
function rescaleRange(x, a, b, c, d){
|
||||
return c + (x - a) * (d - c) / (b - a);
|
||||
}
|
||||
|
||||
/**
|
||||
If value is not undefined return it parsed as a float, otherwise returns undefined.
|
||||
*/
|
||||
function maybeFloat(value) {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return Number.parseFloat(value);
|
||||
}
|
||||
|
||||
function setViewportOffset(viewport, windowHeight) {
|
||||
|
||||
/*
|
||||
Ignoring updates if the the bounding height of the viewport element is
|
||||
not inside of the visible client area on the page to optimise for when
|
||||
it not visible due to being off screen.
|
||||
*/
|
||||
const viewportRect = viewport.getBoundingClientRect();
|
||||
if (viewportRect.top > window.innerHeight || viewportRect.bottom < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate the distance from the top of the viewport to the center of the
|
||||
screen. This is an accurate rendering of perspective shift as you
|
||||
scroll.
|
||||
|
||||
If the the bounds of a viewport element is close to the top or bottom
|
||||
of the page the perspective origin is adjusted so that it is still
|
||||
contained inside of the bounds of the element to not look out of place.
|
||||
endOffset controls how much into the bounds the perspective origin should be
|
||||
pushed when the screen in scrolled all the way to the top/bottom.
|
||||
*/
|
||||
const documentRect = document.documentElement.getBoundingClientRect();
|
||||
const endOffset = viewportRect.height * 0.25;
|
||||
let origin = Math.min(
|
||||
(viewportRect.bottom - documentRect.top) - endOffset,
|
||||
Math.max(
|
||||
windowHeight / 2,
|
||||
windowHeight - (documentRect.bottom - viewportRect.top) + endOffset,
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
In some cases it's undesirable to let the perspective origin move too far
|
||||
away in one or both directions as it causes elements inside the 3d view to
|
||||
become obscured by the top/bottom of the viewport. To aid with this
|
||||
optionally clamp how far the perspective origin can move relative to the top
|
||||
and bottom.
|
||||
|
||||
This is done by calculating how far the origin can go un-clamped while the
|
||||
viewport is visible on the screen and then rescaling the range of the
|
||||
perspective origin from it's un-clamped range to its clamped range.
|
||||
*/
|
||||
const clampTop = maybeFloat(viewport.dataset.clampTop);
|
||||
const clampBottom = maybeFloat(viewport.dataset.clampBottom);
|
||||
if (clampBottom !== undefined || clampTop !== undefined) {
|
||||
const rootFontSize = Number.parseFloat(getComputedStyle(document.documentElement).fontSize);
|
||||
const topOrigin = Math.min(
|
||||
viewportRect.bottom - endOffset,
|
||||
Math.max(viewportRect.top, documentRect.top + windowHeight) - windowHeight / 2,
|
||||
)
|
||||
const topClampPosition = rootFontSize * (clampTop ?? -Infinity) + viewportRect.top;
|
||||
const topLimit = Math.max(topOrigin, topClampPosition);
|
||||
|
||||
const bottomOrigin = Math.max(
|
||||
viewportRect.top + endOffset,
|
||||
Math.min(viewportRect.bottom, documentRect.bottom - windowHeight) + windowHeight / 2,
|
||||
)
|
||||
const bottomClampPosition = rootFontSize * (clampBottom ?? +Infinity) + viewportRect.bottom;
|
||||
const bottomLimit = Math.min(bottomOrigin, bottomClampPosition);
|
||||
|
||||
origin = rescaleRange(origin, topOrigin, bottomOrigin, topLimit, bottomLimit);
|
||||
}
|
||||
|
||||
const yOffset = origin - viewportRect.top;
|
||||
viewport.style.setProperty('perspective-origin', `50% ${yOffset}px`);
|
||||
}
|
||||
|
||||
const viewports = document.querySelectorAll(".viewport");
|
||||
function updateViewports(event) {
|
||||
const windowHeight = getWindowHeight(event);
|
||||
for (const viewport of viewports) {
|
||||
setViewportOffset(viewport, windowHeight);
|
||||
}
|
||||
}
|
||||
window.addEventListener("scroll", updateViewports, { passive: true });
|
||||
window.addEventListener("resize", updateViewports, { passive: true });
|
||||
updateViewports();
|
140
web/assets/styles/base.css
Normal file
140
web/assets/styles/base.css
Normal file
|
@ -0,0 +1,140 @@
|
|||
/* CSS Reset based on https://piccalil.li/blog/a-more-modern-css-reset/ */
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
/* Kill all default margins and paddings */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
-moz-text-size-adjust: none;
|
||||
-webkit-text-size-adjust: none;
|
||||
text-size-adjust: none;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, button, input, label {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
img, svg, picture {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
input, button, textarea, select {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
textarea:not([rows]) {
|
||||
min-height: 10em;
|
||||
}
|
||||
|
||||
:target {
|
||||
scroll-margin-block: 2.5em;
|
||||
}
|
||||
|
||||
/* Overall styling */
|
||||
html {
|
||||
color-scheme: light dark;
|
||||
font-family: sans-serif;
|
||||
overflow-wrap: break-word;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
hgroup h1 {
|
||||
margin-bottom: 0.1em;
|
||||
}
|
||||
hgroup p {
|
||||
font-style: italic;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
margin-block-start: 1.25em;
|
||||
margin-block-end: 0.5em;
|
||||
}
|
||||
|
||||
:is(p, ol, ul) + :is(p, ol, ul) {
|
||||
padding-block-start: 1em;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
padding-inline-start: 1.25em;
|
||||
}
|
||||
|
||||
/* 3d viewport */
|
||||
.in3d {
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
.viewport {
|
||||
pointer-events: none;
|
||||
perspective: 300vmax;
|
||||
}
|
||||
.viewport * {
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
/* Base Page Layout */
|
||||
body {
|
||||
max-width: 50rem;
|
||||
padding: 0;
|
||||
margin-block: 0;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding-block: 1em;
|
||||
}
|
||||
|
||||
.hero {
|
||||
height: 30em;
|
||||
background-color: grey;
|
||||
}
|
||||
|
||||
/* index */
|
||||
.author {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
align-items: center;
|
||||
gap: 1em;
|
||||
margin-block: 1em;
|
||||
}
|
||||
.author h1 {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
.author p {
|
||||
margin-block-end: 0;
|
||||
}
|
||||
|
||||
/* sandbox */
|
||||
.sandbox-inset-3d {
|
||||
contain: paint;
|
||||
background: black;
|
||||
}
|
||||
.sandbox-inset-3d .content {
|
||||
background: grey;
|
||||
transform: translateZ(-10rem);
|
||||
padding: 1rem 1rem;
|
||||
width: 80%;
|
||||
margin-inline: auto;
|
||||
}
|
||||
.sandbox-inset-3d div:where(.floor, .ceiling) {
|
||||
position: absolute;
|
||||
inset-inline: 0;
|
||||
height: 20rem;
|
||||
}
|
||||
.sandbox-inset-3d .floor {
|
||||
bottom: 0;
|
||||
background: blue;
|
||||
transform: rotateX(90deg);
|
||||
transform-origin: 0 100%;
|
||||
}
|
||||
.sandbox-inset-3d .ceiling {
|
||||
top: 0;
|
||||
background: green;
|
||||
transform: rotateX(-90deg);
|
||||
transform-origin: 0 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue