diff --git a/src/app/configs/app.config.ts b/src/app/configs/app.config.ts index d9e860c..823be93 100644 --- a/src/app/configs/app.config.ts +++ b/src/app/configs/app.config.ts @@ -137,5 +137,101 @@ export default { line3: '#efe2df', highlightTheme: 'base16/atelier-cave-light', }, + americana: { + displayName: "Americana", + background: '#f6f4f3', + backgroundOffset: 'rgba(120, 153, 185, 0.1)', + backgroundOffset2: 'rgba(120, 153, 185, 0.2)', + hero: '#17396e', + font: '#af5b5b', + fontMuted: '#276fbf', + box: '#7899b9', + link: 'var(--c-hero)', + noiseSize: '100px', + line3: '#af5b5b', + line1: '#17396e', + line2: '#276fbf', + highlightTheme: 'base16/atelier-cave-light', + }, + ubuntu: { + displayName: "Ubuntu", + background: '#333333', + backgroundOffset: 'rgba(51, 51, 51, 0.4)', + backgroundOffset2: 'rgba(51, 51, 51, 0.6)', + hero: '#e95420', + font: '#ccc', + fontMuted: '#aea79f', + box: '#604358', + link: 'var(--c-hero)', + noiseSize: '100px', + line1: '#56334B', + line2: '#7E5273', + line3: '#ED764D', + highlightTheme: 'base16/atelier-cave-light', + }, + mintMono: { + displayName: "Mint Mono", + background: '#6b9080', + backgroundOffset: 'rgba(164, 195, 178, 0.2)', + backgroundOffset2: 'rgba(51, 51, 51, 0.6)', + hero: '#eaf4f4', + font: '#cce3de', + fontMuted: '#a4c3b2', + box: '#a4c3b2', + link: 'var(--c-hero)', + noiseSize: '100px', + line1: '#a4c3b2', + line2: '#cce3de', + line3: '#f6fff8', + highlightTheme: 'base16/atelier-cave-light', + }, + abyss: { + displayName: "Abyss", + background: '#010A19', + backgroundOffset: 'rgba(1, 10, 25, 0.2)', + backgroundOffset2: 'rgba(1, 10, 25, 0.6)', + hero: '#6c7a96', + font: '#afbed3', + fontMuted: '#8391a7', + box: '#061021', + link: '#8391a7', + noiseSize: '100px', + line1: '#19253b', + line2: '#0b1629', + line3: '#061021', + highlightTheme: 'base16/atelier-cave-light', + }, + blackIsBack: { + displayName: 'Black Is The New Black', + background: '#e4e4e4', + backgroundOffset: 'rgba(188, 188, 188, 0.2)', + backgroundOffset2: 'rgba(188, 188, 188, 0.6)', + hero: '#111', + font: '#4c4c4c', + fontMuted: '#4c4c4c', + box: '#111', + link: '#111', + noiseSize: '100px', + line1: '#333', + line2: '#222', + line3: '#111', + highlightTheme: 'base16/atelier-cave-light', + }, + noir: { + displayName: "Noir", + background: '#2a333f', + backgroundOffset: 'rgba(56, 53, 60, 0.3)', + backgroundOffset2: 'rgb(45, 80, 96, 0.2)', + hero: '#5c3d51', + font: '#7297a8', + fontMuted: '#557988', + box: '#c3984f', + link: 'var(--c-box)', + noiseSize: '100px', + line1: '#c3984f', + line2: '#2d5060', + line3: '#5c3d51', + highlightTheme: 'base16/atelier-estuary', + }, } as Record, } diff --git a/src/app/http/controllers/Home.controller.ts b/src/app/http/controllers/Home.controller.ts index f74a89e..7dd810a 100644 --- a/src/app/http/controllers/Home.controller.ts +++ b/src/app/http/controllers/Home.controller.ts @@ -161,7 +161,7 @@ export class Home extends Controller { const themeKeys = Object.keys(themes) const themeName = this.session.get('theme.name') // const themeName = this.request.safe('theme').or(themeKeys[Math.floor(Math.random()*themeKeys.length)]).in(themeKeys) - const theme = themes[themeName] + const theme = themes[themeName] || themes[themeKeys[0]] const themeCSS = ` :root { --c-background: ${theme.background}; diff --git a/src/app/resources/assets/main-70s.css b/src/app/resources/assets/main-70s.css index e014e6e..d5840e5 100644 --- a/src/app/resources/assets/main-70s.css +++ b/src/app/resources/assets/main-70s.css @@ -135,6 +135,7 @@ b { .section-border .section-border-inner-1 { border-top: 15px solid var(--c-line-1); border-bottom: 15px solid var(--c-line-3); + background: var(--c-line-2); transform: rotate(3deg); width: calc(100vw + 30px); margin-left: -15px; @@ -433,6 +434,24 @@ footer .auth-container .profile { margin-right: 15px; margin-top: 15px; } + +footer.theme-stats { + padding-bottom: 3px; + background: var(--c-background-offset-2); +} + +footer.theme-stats ul { + padding: 0; +} + +footer.theme-stats li { + display: inline; + font-family: "Courier New", Courier, monospace; + font-size: 0.8em; + text-transform: uppercase; + color: var(--c-font-muted); +} + .button-links { margin: 20px; } @@ -466,6 +485,9 @@ footer .auth-container .profile { footer .links { padding: 20px 0 0; } + footer.theme-stats li { + display: block; + } h2 { font-size: 36pt; line-height: 1.6em; @@ -488,6 +510,27 @@ footer .auth-container .profile { background-color: var(--c-background-offset); margin-bottom: 120px; } +.hero .stars svg { + color: var(--c-hero) !important; + position: absolute; + top: 0; + left: 0; + opacity: 0.2; +} + + +#star-1, #star-3, #star-5 { + height: 50px; +} + +#star-2, #star-6 { + height: 75px; +} + +#star-4 { + height: 100px; +} + .hero h1 { font-weight: 300; font-size: 9em; diff --git a/src/app/resources/assets/star.svg b/src/app/resources/assets/star.svg new file mode 100644 index 0000000..bb4bcb4 --- /dev/null +++ b/src/app/resources/assets/star.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + diff --git a/src/app/resources/assets/welcome.js b/src/app/resources/assets/welcome.js index e483942..331eeea 100644 --- a/src/app/resources/assets/welcome.js +++ b/src/app/resources/assets/welcome.js @@ -1,5 +1,7 @@ window.glmdev = window.glmdev || {} +window.glmdev.themeStats = window.glmdev.themeStats || [] + window.glmdev.taglines = [ '...is proud that this site is Google-free', '...is a supporter of FLOSS', @@ -20,7 +22,7 @@ window.glmdev.taglines = [ ] document.querySelector('#tagline') - .addEventListener('click', event => { + ?.addEventListener('click', event => { if ( typeof glmdev.tagline_index === 'undefined' ) glmdev.tagline_index = 0 else if ( glmdev.tagline_index === glmdev.taglines.length - 1 ) glmdev.tagline_index = 0 else glmdev.tagline_index += 1 @@ -29,7 +31,7 @@ document.querySelector('#tagline') }) document.querySelector('#timeline-view-all') - .addEventListener('click', event => { + ?.addEventListener('click', event => { const hidden = document.querySelectorAll('.work-container.theme-hide') for ( const item of hidden ) { item.classList.remove('theme-hide') @@ -37,3 +39,125 @@ document.querySelector('#timeline-view-all') document.querySelector('#timeline-view-all').classList.add('theme-hide') }, false) + +function getRandomArbitrary(min, max) { + min = Math.ceil(min) + max = Math.floor(max) + return Math.floor(Math.random() * (max - min + 1)) + min +} + +const calculateMean = (values) => { + return (values.reduce((sum, current) => sum + current)) / values.length; +} + +const calculateVariance = (values) => { + const average = calculateMean(values); + const squareDiffs = values.map((value) => { + const diff = value - average; + return diff * diff; + }); + return calculateMean(squareDiffs); +} + +const calculateStdev = (values) => { + return Math.sqrt(calculateVariance(values)) +} + +function calculateDistance(ax, ay, bx, by) { + return Math.sqrt(Math.pow(ax-bx, 2) + Math.pow(ay-by, 2)) +} + +function placeStars() { + const widths = [] + const heights = [] + + document.querySelectorAll('.stars svg') + .forEach(function(star) { + var width = window.innerWidth - 50 + var height = (window.innerHeight / 3) - 50 + + var widthOffset = getRandomArbitrary(50, width) + var heightOffset = getRandomArbitrary(50, height) + + star.style.left = widthOffset + 'px' + star.style.top = heightOffset + 'px' + + widths.push(widthOffset) + heights.push(heightOffset) + }) + + return [widths, heights] +} + +function isGoodStarPattern(widths, heights) { + const starCount = document.querySelectorAll('.stars svg').length + const minWidthStdev = window.innerWidth / (starCount / 1.5) + const widthStdev = calculateStdev(widths) + + const minHeightStdev = (window.innerHeight / 3) / (starCount) + const heightStdev = calculateStdev(heights) + + const [centroidWidth, centroidHeight] = [window.innerWidth / 2, (window.innerHeight / 3) / 2] + const distances = widths.map((w, i) => [w, heights[i]]) + .map(([w, h]) => calculateDistance(w, h, centroidWidth, centroidHeight)) + + const minDistanceStdev = Math.min(window.innerWidth, window.innerHeight) / 10 + const distanceStdev = calculateStdev(distances) + + return widthStdev > minWidthStdev && heightStdev > minHeightStdev && distanceStdev > minDistanceStdev +} + +function placeStarsWithRetry() { + if ( !document.querySelectorAll('.stars svg').length ) { + return + } + + let tries = 30 + do { + var [widths, heights] = placeStars() + tries -= 1 + } while ( !isGoodStarPattern(widths, heights) && tries > 0 ) + + const [centroidWidth, centroidHeight] = [window.innerWidth / 2, (window.innerHeight / 3) / 2] + const distances = widths.map((w, i) => [w, heights[i]]) + .map(([w, h]) => calculateDistance(w, h, centroidWidth, centroidHeight)) + + window.glmdev.themeStats.push( + `# of constellations generated: ${30 - tries}`, + `Width stdev: ${Math.round(calculateStdev(widths))}`, + `Height stdev: ${Math.round(calculateStdev(heights))}`, + `Distance stdev: ${Math.round(calculateStdev(distances))}` + ) +} + +window.placeStars = placeStars +window.isGoodStarPattern = isGoodStarPattern +window.placeStarsWithRetry = placeStarsWithRetry +placeStarsWithRetry() + +function updateThemeStatsDisplay() { + const ul = document.querySelector('footer.theme-stats ul') + + for ( const stat of glmdev.themeStats ) { + const li = document.createElement('li') + li.innerText = stat + ul.appendChild(li) + } + + ul.style.display = glmdev.themeStats.length ? 'unset' : 'none' +} + +var resizeDebufHandle = false +window.onresize = () => { + if ( resizeDebufHandle ) { + clearTimeout(resizeDebufHandle) + } + + resizeDebufHandle = setTimeout(() => { + placeStarsWithRetry() + resizeDebufHandle = false + }, 500) +} + +window.updateThemeStatsDisplay = updateThemeStatsDisplay +updateThemeStatsDisplay() diff --git a/src/app/resources/views/technical.pug b/src/app/resources/views/technical.pug index db26e50..dccf377 100644 --- a/src/app/resources/views/technical.pug +++ b/src/app/resources/views/technical.pug @@ -24,6 +24,15 @@ block content br br + h4 The Stars + p The homepage of this site features a constellation of 6 translucent stars which appears at the top of the page. + p The positions of the stars are randomly generated based on the dimensions of the page. To discourage clustered/skewed constellations, the following criteria are used: + ol + li Standard deviation of the x-axis offset + li Standard deviation of the y-axis offset + li Standard deviation of the centroid distance + p Because it's interesting, you can view the # of generation attempts, as well as the aforementioned criteria in the footer of the homepage. + h3 Source Code & Licensing a(href='https://creativecommons.org/licenses/by-nc-sa/4.0/' target='_blank' style='margin-top: 15px') img(src=asset('cc-by-nc-sa.png')) diff --git a/src/app/resources/views/template_70s.pug b/src/app/resources/views/template_70s.pug index 8b3c237..81a7e7f 100644 --- a/src/app/resources/views/template_70s.pug +++ b/src/app/resources/views/template_70s.pug @@ -17,6 +17,10 @@ head block style style !{themeCSS} + script. + window.glmdev = window.glmdev || {} + window.glmdev.themeStats = window.glmdev.themeStats || [] + window.glmdev.themeStats.push('Theme: !{themeDisplayName}') link(rel='stylesheet' href=asset('main-70s.css')) link(rel='author' href='/humans.txt') @@ -81,5 +85,8 @@ body li a(href='/dash') dashboard + footer.theme-stats + ul + block script script(src=asset('welcome.js')) diff --git a/src/app/resources/views/welcome.pug b/src/app/resources/views/welcome.pug index e6c6fdd..0c7488a 100644 --- a/src/app/resources/views/welcome.pug +++ b/src/app/resources/views/welcome.pug @@ -3,6 +3,17 @@ block content .container#home .inner section.hero.full-height + .stars + each val in [1, 2, 3, 4, 5, 6] + svg(id=('star-' + val) width='47.581146mm' height='78.547104mm' viewbox='0 0 47.581146 78.547104' version='1.1' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg') + sodipodi:namedview#namedview7(pagecolor='#ffffff' bordercolor='currentColor' borderopacity='0.25' inkscape:showpageshadow='2' inkscape:pageopacity='0.0' inkscape:pagecheckerboard='0' inkscape:deskcolor='#d1d1d1' inkscape:document-units='mm' showgrid='false') + defs#defs2 + g#layer1(inkscape:label='Layer 1' inkscape:groupmode='layer' transform='translate(-81.41733,-109.42827)') + path#path1153(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105,112.92827 c 0,0 1.66434,35.57173 -20.082663,35.57173' inkscape:export-filename='star.svg' inkscape:export-xdpi='96' inkscape:export-ydpi='96') + path#path1153-3(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105.41582,112.92827 c 0,0 -1.66434,35.57173 20.08267,35.57173') + path#path1153-6(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105,184.47538 c 0,0 1.66434,-35.57173 -20.082665,-35.57173') + path#path1153-3-7(style='fill:currentColor;fill-opacity:0;stroke:currentColor;stroke-width:7;stroke-linecap:round;stroke-dasharray:none' d='m 105.41582,184.47538 c 0,0 -1.66434,-35.57173 20.08267,-35.57173') + .hero-box h1 Garrett Mills p Software engineer, computer scientist, and nerd.