Skip to content

Instantly share code, notes, and snippets.

@scriptype
Created May 7, 2020 02:55
Show Gist options
  • Save scriptype/cd179a7db3b15a0cab481c779cf66f3b to your computer and use it in GitHub Desktop.
Save scriptype/cd179a7db3b15a0cab481c779cf66f3b to your computer and use it in GitHub Desktop.
Park Street 11 - 3D CSS
<div class="wall">
<div class="wall-rain-shield"></div>
<div id="wall-half-vertical-tiles">
<div class="left-shadow"></div>
</div>
<div id="wall-masonry-vertical-tiles">
<div class="left-shadow"></div>
</div>
<div id="wall-half-horizontal-tiles">
<div class="left-shadow"></div>
</div>
<div id="wall-single-horizontal-tiles">
<div class="left-shadow"></div>
</div>
<div id="wall-front-tiles">
<div class="left-shadow"></div>
<div class="window">
<div class="left-shadow"></div>
<div class="window-reflection-container">
<img class="window-reflection" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/25387/frederick-tubiermont-mfDhj3o8LoI-unsplash.jpg" alt="Photo of a street with trees" />
</div>
<div class="window-frame"></div>
</div>
<div class="ventilation-grid"></div>
<div class="street-signs">
<p class="street-sign fin">puistokatu</p>
<p class="street-sign swe">parkgatan</p>
</div>
</div>
</div>
const query = document.querySelector.bind(document)
const queryAll = document.querySelectorAll.bind(document)
const { random } = Math
const nArray = n => [...Array(n).keys()]
const setCSSProperty = (key, value) => {
document.documentElement.style.setProperty(key, value)
}
const elementCreatorFromHTML = () => {
var el = document.createElement('div')
return html => {
el.innerHTML = html
return el.firstElementChild
}
}
const createElementFromHTML = elementCreatorFromHTML()
const setRandomTileColor = element => {
const darkCent = 15 + random() * 15
const lightCent = 20 + random() * 20
const hsla = [
5 + random() * 10,
`${ 30 + random() * 10 }%`,
`${ random() >= .06 ? lightCent : darkCent }%`,
1
]
element.style.cssText += `
background-color: hsla(${hsla.join(', ')})
`
return element
}
const templates = {
halfVertical() {
return `
<div class="tile half-vertical">
<span></span>
</div>
`
},
masonryVertical({ isAlternate } = { isAlternate: false }) {
return `
<div class="tile masonry-vertical ${isAlternate ? 'odd' : 'even'}">
<span></span>
<span></span>
</div>
`
},
halfHorizontal() {
return `
<div class="tile half-horizontal">
<span></span>
</div>
`
},
singleHorizontal() {
return `
<div class="tile single-horizontal">
<span></span>
</div>
`
},
horizontal() {
return `
<div class="tile horizontal">
<span></span>
<span></span>
<span></span>
<span></span>
</div>
`
},
vertical() {
return `
<div class="tile vertical">
<span></span>
<span></span>
<span></span>
</div>
`
}
}
const rowCount = 8
const colCount = 13
const totalTileCount = rowCount * colCount
setCSSProperty('--row-count', rowCount)
setCSSProperty('--col-count', colCount)
window.addEventListener('mousemove', event => {
var { innerWidth, innerHeight } = window
var { clientX, clientY } = event
var relativeX = .5 - clientX / innerWidth
var relativeY = .5 - clientY / innerHeight
setCSSProperty('--camera-x', relativeX)
setCSSProperty('--camera-y', relativeY)
})
const renderTiles = ({ targetEl, templateFn, tileCount }) => {
const html = nArray(tileCount).map(i => {
const template = templateFn(i)
const tile = createElementFromHTML(template)
tile.querySelectorAll('span').forEach(setRandomTileColor)
return tile.outerHTML
}).join('')
targetEl.innerHTML += html
}
renderTiles({
targetEl: query('#wall-front-tiles'),
tileCount: totalTileCount,
templateFn: i => (
i % 2 ? templates.horizontal() : templates.vertical()
)
})
renderTiles({
targetEl: query('#wall-single-horizontal-tiles'),
tileCount: colCount,
templateFn: () => templates.singleHorizontal()
})
renderTiles({
targetEl: query('#wall-half-horizontal-tiles'),
tileCount: colCount * 2,
templateFn: () => templates.halfHorizontal()
})
renderTiles({
targetEl: query('#wall-masonry-vertical-tiles'),
tileCount: colCount * 3,
templateFn: i => (
templates.masonryVertical({
isAlternate: !!(i % 2)
})
)
})
renderTiles({
targetEl: query('#wall-half-vertical-tiles'),
tileCount: colCount * 3,
templateFn: () => templates.halfVertical()
})
// Tree Photo by Frederick Tubiermont on Unsplash
// This is the most similar free alternative to original Linsingen Vintage font used in actual street signs in Finland.
@font-face {
font-family: 'VenturisSansADFLt-Bold';
src:url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/25387/VenturisSansADFLt-Bold.ttf.woff') format('woff'),
url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/25387/VenturisSansADFLt-Bold.ttf.svg#VenturisSansADFLt-Bold') format('svg'),
url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/25387/VenturisSansADFLt-Bold.ttf.eot'),
url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/25387/VenturisSansADFLt-Bold.ttf.eot?#iefix') format('embedded-opentype');
font-weight: normal;
font-style: normal;
}
:root {
--camera-x: 0; // will be updated by JS
--camera-y: 0; // will be updated by JS
--row-count: 0; // will be set from JS
--col-count: 0; // will be set from JS
--tile-width: 1em;
--gap-width: calc(var(--tile-width) * 5 / 100);
--total-tile-width: calc(1em + var(--gap-width));
--gap-color: #666;
}
@function tile($n) {
@return calc(#{$n} * var(--tile-width));
}
@function totalTile($n) {
@return calc(#{$n} * var(--total-tile-width));
}
body {
min-height: 100vh;
display: grid;
justify-content: center;
align-items: center;
font-size: 7vmin;
perspective: 150vmin;
background: lighten(cadetblue, 35%);
}
.wall {
position: relative;
background: var(--gap-color);
transform-style: preserve-3d;
transform:
rotateX( calc(var(--camera-y) * -15deg) )
rotateY( calc(var(--camera-x) * 15deg) );
}
.wall-rain-shield {
position: absolute;
top: tile(-.26);
left: 0;
z-index: 5;
width: 100%;
height: tile(.4);
background: #bbb;
transform-origin: top center;
transform:
translateZ(tile(.6))
rotateX(30deg);
}
#wall-half-vertical-tiles {
position: relative;
z-index: 4;
display: grid;
gap: var(--gap-width);
grid-template: 1fr / repeat(calc(3 * var(--col-count)), 1fr);
box-shadow: 0 tile(.25) tile(.1) rgba(#000, .5);
transform: translateZ(tile(.6));
background: var(--gap-color);
// Shadow of rain shield ¯\_(ツ)_/¯
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow: inset 0 tile(.2) tile(.1) rgba(#000, .5),
}
}
#wall-masonry-vertical-tiles {
position: relative;
z-index: 3;
display: grid;
gap: var(--gap-width);
grid-template: 1fr / repeat(calc(3 * var(--col-count)), 1fr);
box-shadow: 0 tile(.25) tile(.1) rgba(#000, .5);
transform: translateZ(tile(.45));
}
#wall-half-horizontal-tiles {
position: relative;
z-index: 2;
display: grid;
gap: var(--gap-width);
grid-template: 1fr / repeat(calc(2 * var(--col-count)), 1fr);
box-shadow: 0 tile(.25) tile(.1) rgba(#000, .5);
transform: translateZ(tile(.3));
}
#wall-single-horizontal-tiles {
position: relative;
z-index: 1;
display: grid;
gap: var(--gap-width);
grid-template: 1fr / repeat(var(--col-count), 1fr);
box-shadow: 0 tile(.25) tile(.1) rgba(#000, .5);
transform: translateZ(tile(.15));
}
#wall-front-tiles {
position: relative;
display: grid;
gap: var(--gap-width);
grid-template:
repeat(var(--row-count), 1fr) /
repeat(var(--col-count), 1fr);
}
.left-shadow {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
box-shadow: inset tile(1.65) 0 tile(.08) rgba(#000, .5);
transform: translateZ(1px);
}
.window {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: totalTile(5);
height: totalTile(6);
overflow: hidden;
background: darken(desaturate(skyblue, 40%), 60%);
perspective: 150vmin;
.left-shadow {
left: tile(-1.5);
}
}
.window-reflection-container {
position: relative;
display: block;
width: 100%;
height: 100%;
}
.window-reflection {
position: absolute;
top: tile(-5);
right: tile(-1);
max-width: 600%;
mix-blend-mode: overlay;
transform: translate(
calc(var(--camera-x) * -2%),
calc(var(--camera-y) * -2%)
);
}
.window-frame {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
box-shadow:
inset 0 0 0 tile(.25) #eae4da,
inset tile(.01) tile(.01) 0 tile(.255) rgba(#000, .5),
inset 0 0 0 tile(.5) #eae4da,
inset 0 0 0 tile(.6) darken(desaturate(orange, 50%), 10%);
}
.ventilation-grid {
position: absolute;
top: 0;
right: tile(1.2);
width: tile(.8);
height: tile(.8);
background: #777;
&::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 80%;
height: 80%;
transform: translate(-50%, -50%);
background: repeating-linear-gradient(
to bottom,
rgba(#000, .8),
transparent tile(.1),
transparent tile(.14)
);
}
}
.street-signs {
position: absolute;
left: tile(.5);
bottom: tile(1);
height: tile(.5);
}
.street-sign {
position: relative;
padding: tile(.25) tile(.3);
background: linear-gradient(to bottom, #eae4da, #dad4ca);
font: normal tile(.4)/tile(.6) 'VenturisSansADFLt-Bold', sans-serif;
text-transform: uppercase;
box-shadow: tile(.02) tile(.02) 2px rgba(#000, .5);
&::before, &::after {
content: "";
position: absolute;
top: 50%;
transform: translateY(-50%);
width: tile(.15);
height: tile(.15);
background: #333;
border-radius: 100%;
}
&::before {
left: tile(.01);
}
&::after {
right: tile(.01);
}
&.swe {
letter-spacing: tile(.06);
}
}
.tile {
width: 1em;
height: 1em;
display: grid;
gap: var(--gap-width);
background: var(--gap-color);
span {
$light-shadow-pos: tile(.02) tile(.02) tile(.06);
$dark-shadow-pos: tile(-.02) tile(-.02) tile(.08);
background: currentColor;
box-shadow:
inset $light-shadow-pos rgba(255, 200, 50, .25),
inset $dark-shadow-pos rgba(30, 10, 0, .5);
}
}
.tile.half-vertical {
width: tile(.3);
height: tile(.4);
}
.tile.masonry-vertical {
grid-template: .6fr 1.4fr / 1fr;
width: tile(.3);
&.odd {
grid-template: 1.4fr .6fr / 1fr;
}
}
.tile.half-horizontal {
width: tile(.475);
height: tile(.33);
}
.tile.single-horizontal {
height: tile(.33);
}
.tile.vertical {
grid-template: 1fr / repeat(3, 1fr);
}
.tile.horizontal {
grid-template: repeat(3, 1fr) / 1fr 1fr;
:first-child {
grid-area: 1 / span 2;
}
:last-child {
grid-area: 3 / span 2;
}
}
.with-sides {
.side {
position: absolute;
top: 0;
left: 0;
width: tile(3);
height: inherit;
overflow: hidden;
display: inherit;
background: var(--gap-color);
grid-template: inherit;
gap: inherit;
transform-origin: top left;
transform: rotateY(90deg);
&:last-child {
left: auto;
right: 0;
transform-origin: top right;
transform: rotateY(-90deg);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment