All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
317 lines
6.4 KiB
Svelte
317 lines
6.4 KiB
Svelte
<script lang="ts">
|
|
import _ from 'lodash';
|
|
import { createEventDispatcher, onMount } from 'svelte';
|
|
const dispatch = createEventDispatcher();
|
|
|
|
const START_COUNT = 27;
|
|
const MAX_COUNT = 33;
|
|
const MARGIN_SIZE = 16;
|
|
|
|
let horizontalCount = START_COUNT;
|
|
let verticalCount = START_COUNT;
|
|
let blockSize = 64;
|
|
let cornerBlocks = 2;
|
|
|
|
let horizontalMargin = MARGIN_SIZE;
|
|
let verticalMargin = MARGIN_SIZE;
|
|
let unloaded = true;
|
|
|
|
export let transparent = false;
|
|
export let subdued = false;
|
|
|
|
function updateCounts() {
|
|
const gridWidth = window.innerWidth - MARGIN_SIZE;
|
|
const gridHeight = window.innerHeight - MARGIN_SIZE;
|
|
|
|
const [longerSide, shorterSide] =
|
|
gridWidth > gridHeight ? [gridWidth, gridHeight] : [gridHeight, gridWidth];
|
|
|
|
function discrepancy(count: number) {
|
|
const blockSize = longerSide / count;
|
|
return shorterSide / blockSize - Math.floor(shorterSide / blockSize);
|
|
}
|
|
|
|
let finalCount = START_COUNT;
|
|
let count = START_COUNT;
|
|
let bestDiscrepancy = discrepancy(count);
|
|
while (count < MAX_COUNT) {
|
|
count += 2;
|
|
const currentDiscrepancy = discrepancy(count);
|
|
if (currentDiscrepancy < bestDiscrepancy) {
|
|
bestDiscrepancy = currentDiscrepancy;
|
|
finalCount = count;
|
|
}
|
|
}
|
|
|
|
const longerCount = finalCount;
|
|
const shorterCount = Math.round(longerCount * (shorterSide / longerSide));
|
|
|
|
[horizontalCount, verticalCount] =
|
|
gridWidth > gridHeight ? [longerCount, shorterCount] : [shorterCount, longerCount];
|
|
blockSize = longerSide / finalCount;
|
|
horizontalMargin = (window.innerWidth - blockSize * horizontalCount) / 2;
|
|
verticalMargin = (window.innerHeight - blockSize * verticalCount) / 2;
|
|
cornerBlocks = shorterCount > 8 ? 3 : Math.max(1, Math.floor(shorterCount / 4));
|
|
dispatch('change', {
|
|
horizontalCount,
|
|
verticalCount,
|
|
blockSize,
|
|
horizontalMargin,
|
|
verticalMargin
|
|
});
|
|
}
|
|
const updateCountsOnResize = _.throttle(updateCounts, 200);
|
|
|
|
onMount(() => {
|
|
window.addEventListener('resize', updateCountsOnResize);
|
|
updateCounts();
|
|
unloaded = false;
|
|
});
|
|
</script>
|
|
|
|
<div
|
|
class="background"
|
|
class:unloaded
|
|
class:transparent
|
|
class:subdued
|
|
class:even-vertical={verticalCount % 2 === 0}
|
|
style="--horizontal-count: {horizontalCount};
|
|
--vertical-count: {verticalCount};
|
|
--block-size: {blockSize}px;
|
|
--horizontal-margin: {horizontalMargin}px;
|
|
--vertical-margin: {verticalMargin}px;"
|
|
>
|
|
<div class="corners">
|
|
<div class="corner top left"></div>
|
|
<div class="corner top right"></div>
|
|
<div class="corner bottom left"></div>
|
|
<div class="corner bottom right"></div>
|
|
</div>
|
|
<div class="edges">
|
|
{#each ['top', 'bottom'] as edge}
|
|
<div class="edge {edge}">
|
|
{#each [...Array(horizontalCount).keys()] as n}
|
|
<div
|
|
class="block"
|
|
class:corner-block={n < cornerBlocks || n >= horizontalCount - cornerBlocks}
|
|
></div>
|
|
{/each}
|
|
</div>
|
|
{/each}
|
|
{#each ['left', 'right'] as edge}
|
|
<div class="edge {edge}">
|
|
{#each [...Array(verticalCount).keys()] as n}
|
|
<div
|
|
class="block"
|
|
class:corner-block={n < cornerBlocks || n >= verticalCount - cornerBlocks}
|
|
></div>
|
|
{/each}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
<div class="grid">
|
|
{#each [...Array(verticalCount).keys()] as x}
|
|
<div class="row" data-idx="{x}">
|
|
{#each [...Array(horizontalCount).keys()] as y}
|
|
<div class="block" data-idx="{y}"></div>
|
|
{/each}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.background {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
|
|
pointer-events: none;
|
|
|
|
--corner-color: white;
|
|
}
|
|
|
|
.corner {
|
|
background-color: var(--corner-color);
|
|
|
|
position: absolute;
|
|
&.top {
|
|
top: 0;
|
|
height: var(--vertical-margin);
|
|
}
|
|
&.bottom {
|
|
bottom: 0;
|
|
height: var(--vertical-margin);
|
|
}
|
|
&.left {
|
|
left: 0;
|
|
width: var(--horizontal-margin);
|
|
}
|
|
&.right {
|
|
right: 0;
|
|
width: var(--horizontal-margin);
|
|
}
|
|
}
|
|
|
|
.edges {
|
|
& .edge {
|
|
position: absolute;
|
|
display: flex;
|
|
}
|
|
|
|
& .top {
|
|
top: 0;
|
|
}
|
|
|
|
& .bottom {
|
|
bottom: 0;
|
|
}
|
|
|
|
& .top,
|
|
& .bottom {
|
|
width: calc(100vw - var(--horizontal-margin) * 2);
|
|
height: calc(var(--margin-size) * 2);
|
|
left: var(--horizontal-margin);
|
|
|
|
& .block {
|
|
width: var(--block-size);
|
|
height: var(--vertical-margin);
|
|
}
|
|
}
|
|
|
|
& .left {
|
|
left: 0;
|
|
}
|
|
|
|
& .right {
|
|
right: 0;
|
|
}
|
|
|
|
& .left,
|
|
& .right {
|
|
flex-direction: column;
|
|
height: calc(100vh - var(--vertical-margin) * 2);
|
|
width: calc(var(--margin-size) * 2);
|
|
top: var(--vertical-margin);
|
|
|
|
& .block {
|
|
width: var(--horizontal-margin);
|
|
height: var(--block-size);
|
|
}
|
|
}
|
|
|
|
& .block {
|
|
display: inline-block;
|
|
|
|
background: black;
|
|
&:nth-child(odd) {
|
|
background: white;
|
|
}
|
|
|
|
&.corner-block {
|
|
background: white;
|
|
}
|
|
}
|
|
}
|
|
|
|
.block,
|
|
.corner {
|
|
transition: background 1s;
|
|
}
|
|
|
|
.background.even-vertical .edges .right .block:not(.corner-block) {
|
|
background: white;
|
|
&:nth-child(odd) {
|
|
background: black;
|
|
}
|
|
}
|
|
|
|
.background.transparent {
|
|
& .block {
|
|
background: transparent !important;
|
|
}
|
|
|
|
& .edge {
|
|
&.top .block {
|
|
border-right: 1px solid gray;
|
|
&:first-child {
|
|
border-left: 1px solid gray;
|
|
}
|
|
}
|
|
|
|
&.bottom .block {
|
|
border-right: 1px solid gray;
|
|
&:first-child {
|
|
border-left: 1px solid gray;
|
|
}
|
|
}
|
|
|
|
&.left .block {
|
|
border-bottom: 1px solid gray;
|
|
&:first-child {
|
|
border-top: 1px solid gray;
|
|
}
|
|
}
|
|
|
|
&.right .block {
|
|
border-bottom: 1px solid gray;
|
|
&:first-child {
|
|
border-top: 1px solid gray;
|
|
}
|
|
}
|
|
}
|
|
|
|
& .corner {
|
|
background: transparent !important;
|
|
}
|
|
}
|
|
|
|
.background.subdued {
|
|
& .edge,
|
|
& .corner {
|
|
opacity: 0.33;
|
|
}
|
|
}
|
|
|
|
.grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(var(--horizontal-count), var(--block-size));
|
|
grid-template-rows: repeat(var(--vertical-count), var(--block-size));
|
|
position: absolute;
|
|
top: var(--vertical-margin);
|
|
left: var(--horizontal-margin);
|
|
|
|
& .row {
|
|
display: contents;
|
|
}
|
|
|
|
& .block {
|
|
background: black;
|
|
border-right: 1px solid gray;
|
|
border-bottom: 1px solid gray;
|
|
}
|
|
|
|
& .row:first-child .block {
|
|
border-top: 1px solid gray;
|
|
}
|
|
|
|
& .block:first-child {
|
|
border-left: 1px solid gray;
|
|
}
|
|
}
|
|
|
|
.background.unloaded {
|
|
& .grid {
|
|
width: calc(100vw - var(--horizontal-margin) * 2);
|
|
height: calc(100vh - var(--vertical-margin) * 2);
|
|
grid-template-rows: repeat(var(--vertical-count), 1fr);
|
|
grid-template-columns: repeat(var(--horizontal-count), 1fr);
|
|
}
|
|
|
|
& .edge {
|
|
justify-content: space-evenly;
|
|
}
|
|
}
|
|
</style>
|