add more color modes, respect dark mode setting, use button for among
Signed-off-by: Naman Sood <mail@nsood.in>
This commit is contained in:
parent
7f3de55234
commit
294e147d39
5 changed files with 196 additions and 175 deletions
|
@ -5,7 +5,7 @@ This is the code for my personal website and resume. The main branch of this rep
|
|||
## Cool things that I never get to brag about
|
||||
|
||||
* The header section of the website always correctly indicates my age, as long as you have JavaScript enabled. It will calculate and display my age based on my most recent birthday.
|
||||
* There is a dark mode in this website, accessible by typing `clapoff`. Light mode can be restored by typing `clapon`.
|
||||
* There is a dark mode in this website, accessible by typing `dark`. Light mode can be restored by typing `lite`. Other color modes are also available -- `purp`, `yell`, `blue`, `mint` at least. See `js/script.js` for an exhaustive list. Your preference can be stored permanently by typing `save`.
|
||||
* I spent a decent amount of time worrying about accessibility in the later iterations of this website, so hopefully that should not be an issue for anyone. If there's anything I can do to improve on that front, please let me know.
|
||||
|
||||
## License
|
||||
|
|
107
css/style.css
107
css/style.css
|
@ -10,36 +10,59 @@ html {
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: "Nunito", sans-serif;
|
||||
background: var(--bgColor);
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
body {
|
||||
--bgColor: white;
|
||||
--textColor: #777;
|
||||
--iconColor: black;
|
||||
--boldColor: black;
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
--bgColor: black;
|
||||
--iconColor: white;
|
||||
--textColor: #aaa;
|
||||
--boldColor: white;
|
||||
}
|
||||
}
|
||||
body.lite-mode {
|
||||
--bgColor: white;
|
||||
--textColor: #777;
|
||||
--iconColor: black;
|
||||
--boldColor: black;
|
||||
}
|
||||
|
||||
body.dark-mode {
|
||||
background: black;
|
||||
color: white;
|
||||
--bgColor: black;
|
||||
--iconColor: white;
|
||||
--textColor: #aaa;
|
||||
--boldColor: white;
|
||||
}
|
||||
body.dark-mode .bg {
|
||||
opacity: 0 !important;
|
||||
body.mint-mode {
|
||||
--bgColor: #c7fcee;
|
||||
--iconColor: #486b61;
|
||||
--textColor: #5f8c80;
|
||||
--boldColor: #10352b;
|
||||
}
|
||||
body.dark-mode .main h1 {
|
||||
color: #fff;
|
||||
body.purp-mode {
|
||||
--bgColor: #cfc7fc;
|
||||
--iconColor: #5a518c;
|
||||
--textColor: #7166af;
|
||||
--boldColor: #312c4c;
|
||||
}
|
||||
body.dark-mode .main em {
|
||||
color: #fff;
|
||||
body.yell-mode {
|
||||
--bgColor: #fcfcbf;
|
||||
--iconColor: #54543f;
|
||||
--textColor: #8c8c5f;
|
||||
--boldColor: #353528;
|
||||
}
|
||||
body.dark-mode .main h3 {
|
||||
color: #aaa;
|
||||
}
|
||||
body.dark-mode .main h3 a {
|
||||
color: #fff;
|
||||
}
|
||||
body.dark-mode .main h3 a:before, body.dark-mode .main h3 a:after {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
body.dark-mode .main h3 a.among {
|
||||
color: inherit;
|
||||
}
|
||||
body.dark-mode .main a.contact-method p {
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
body.blue-mode {
|
||||
--bgColor: #ade3ff;
|
||||
--iconColor: #394b54;
|
||||
--textColor: #46748c;
|
||||
--boldColor: #242f35;
|
||||
}
|
||||
|
||||
.main {
|
||||
|
@ -69,43 +92,35 @@ body.dark-mode .main a.contact-method p {
|
|||
font-size: 4rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 2rem;
|
||||
color: #000;
|
||||
color: var(--boldColor);
|
||||
}
|
||||
.main h3 {
|
||||
margin: 1rem 0 0;
|
||||
font-size: 1.7rem;
|
||||
line-height: 1.4;
|
||||
font-weight: normal;
|
||||
color: #777;
|
||||
color: var(--textColor);
|
||||
}
|
||||
.main em {
|
||||
color: #000;
|
||||
color: var(--boldColor);
|
||||
font: inherit;
|
||||
}
|
||||
.main h3 a {
|
||||
color: #000;
|
||||
color: var(--boldColor);
|
||||
text-decoration: none;
|
||||
border-bottom: 0.2rem solid transparent;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
}
|
||||
.main h3 a.among {
|
||||
color: inherit;
|
||||
border-bottom: 0;
|
||||
cursor: text;
|
||||
}
|
||||
.main h3 a.among:before, .main h3 a.among:after {
|
||||
content: none;
|
||||
}
|
||||
.main h3 a:before, .main h3 a:after {
|
||||
content: "";
|
||||
bottom: 0.4rem;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 0.1rem;
|
||||
background: rgba(51, 51, 51, 0.2);
|
||||
background: var(--boldColor);
|
||||
position: absolute;
|
||||
opacity: 0.2;
|
||||
opacity: 0.04;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.main h3 a:before {
|
||||
|
@ -113,11 +128,19 @@ body.dark-mode .main a.contact-method p {
|
|||
transform-origin: center left;
|
||||
}
|
||||
.main h3 a:after {
|
||||
opacity: 0.5;
|
||||
opacity: 0.1;
|
||||
}
|
||||
.main h3 a:hover:before {
|
||||
transform: scaleX(1);
|
||||
opacity: 1;
|
||||
opacity: 0.2;
|
||||
}
|
||||
.main h3 button {
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
}
|
||||
.main .contact-methods {
|
||||
display: flex;
|
||||
|
@ -132,6 +155,9 @@ body.dark-mode .main a.contact-method p {
|
|||
width: 4rem;
|
||||
height: 4rem;
|
||||
}
|
||||
.main .contact-methods a.contact-method i {
|
||||
color: var(--iconColor);
|
||||
}
|
||||
.main .contact-methods a.contact-method p {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
|
@ -219,6 +245,7 @@ body.dark-mode .main a.contact-method p {
|
|||
pointer-events: none;
|
||||
transition: none;
|
||||
opacity: 0;
|
||||
color: var(--boldColor);
|
||||
}
|
||||
|
||||
.clapper.clapping {
|
||||
|
|
116
css/style.scss
116
css/style.scss
|
@ -10,45 +10,62 @@ html {
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: 'Nunito', sans-serif;
|
||||
}
|
||||
background: var(--bgColor);
|
||||
|
||||
body.dark-mode {
|
||||
background: black;
|
||||
color: white;
|
||||
|
||||
.bg {
|
||||
opacity: 0 !important;
|
||||
@media(prefers-color-scheme: light) {
|
||||
--bgColor: white;
|
||||
--textColor: #777;
|
||||
--iconColor: black;
|
||||
--boldColor: black;
|
||||
}
|
||||
|
||||
.main {
|
||||
h1 {
|
||||
color: #fff;
|
||||
}
|
||||
@media(prefers-color-scheme: dark) {
|
||||
--bgColor: black;
|
||||
--iconColor: white;
|
||||
--textColor: #aaa;
|
||||
--boldColor: white;
|
||||
}
|
||||
|
||||
em {
|
||||
color: #fff;
|
||||
}
|
||||
&.lite-mode {
|
||||
--bgColor: white;
|
||||
--textColor: #777;
|
||||
--iconColor: black;
|
||||
--boldColor: black;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #aaa;
|
||||
}
|
||||
&.dark-mode {
|
||||
--bgColor: black;
|
||||
--iconColor: white;
|
||||
--textColor: #aaa;
|
||||
--boldColor: white;
|
||||
}
|
||||
|
||||
h3 a {
|
||||
color: #fff;
|
||||
&.mint-mode {
|
||||
--bgColor: #c7fcee;
|
||||
--iconColor: #486b61;
|
||||
--textColor: #5f8c80;
|
||||
--boldColor: #10352b;
|
||||
}
|
||||
|
||||
&:before, &:after {
|
||||
background: rgba(#fff, 0.2);
|
||||
}
|
||||
&.purp-mode {
|
||||
--bgColor: #cfc7fc;
|
||||
--iconColor: #5a518c;
|
||||
--textColor: #7166af;
|
||||
--boldColor: #312c4c;
|
||||
}
|
||||
|
||||
&.among {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
&.yell-mode {
|
||||
--bgColor: #fcfcbf;
|
||||
--iconColor: #54543f;
|
||||
--textColor: #8c8c5f;
|
||||
--boldColor: #353528;
|
||||
}
|
||||
|
||||
a.contact-method p {
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
}
|
||||
&.blue-mode {
|
||||
--bgColor: #ade3ff;
|
||||
--iconColor: #394b54;
|
||||
--textColor: #46748c;
|
||||
--boldColor: #242f35;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +100,7 @@ body.dark-mode {
|
|||
font-size: 4rem;
|
||||
font-weight: 600;
|
||||
margin: 0 0 2rem;
|
||||
color: #000;
|
||||
color: var(--boldColor);
|
||||
}
|
||||
|
||||
h3 {
|
||||
|
@ -91,39 +108,30 @@ body.dark-mode {
|
|||
font-size: 1.7rem;
|
||||
line-height: 1.4;
|
||||
font-weight: normal;
|
||||
color: #777;
|
||||
color: var(--textColor);
|
||||
}
|
||||
|
||||
em {
|
||||
color: #000;
|
||||
color: var(--boldColor);
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
h3 a {
|
||||
color: #000;
|
||||
color: var(--boldColor);
|
||||
text-decoration: none;
|
||||
border-bottom: 0.2rem solid transparent;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
|
||||
&.among {
|
||||
color: inherit;
|
||||
border-bottom: 0;
|
||||
&:before, &:after {
|
||||
content: none;
|
||||
}
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
&:before, &:after {
|
||||
content: '';
|
||||
bottom: 0.4rem;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 0.1rem;
|
||||
background: rgba(#333, 0.2);
|
||||
background: var(--boldColor);
|
||||
position: absolute;
|
||||
opacity: 0.2;
|
||||
opacity: 0.04;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
|
@ -133,15 +141,24 @@ body.dark-mode {
|
|||
}
|
||||
|
||||
&:after {
|
||||
opacity: 0.5;
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
transform: scaleX(1);
|
||||
opacity: 1;
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
h3 button {
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.contact-methods {
|
||||
display: flex;
|
||||
margin-left: -1.5rem;
|
||||
|
@ -155,6 +172,10 @@ body.dark-mode {
|
|||
width: 4rem;
|
||||
height: 4rem;
|
||||
|
||||
i {
|
||||
color: var(--iconColor);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
|
@ -258,6 +279,7 @@ body.dark-mode {
|
|||
pointer-events: none;
|
||||
transition: none;
|
||||
opacity: 0;
|
||||
color: var(--boldColor);
|
||||
}
|
||||
|
||||
.clapper.clapping {
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<canvas id="bg" alt=""></canvas>
|
||||
<div class="text-container">
|
||||
<h1>Hi, I’m Naman.</h1>
|
||||
<h3>I’m a<span class="age"> twenty one</span> year-old who's fond of <em>coffee</em>, <em>math</em>, <em>writing</em>, <em>cars</em>, <a class="among" role="button" href="javascript:among()">among</a> other things. I also enjoy <em>programming</em> enough to do that for a living. I'm currently studying <em>computer science</em> at the <em>University of Waterloo</em>.</h3>
|
||||
<h3>I’m a<span class="age"> twenty one</span> year-old who's fond of <em>coffee</em>, <em>math</em>, <em>writing</em>, <em>cars</em>, <button class="among">among</button> other things. I also enjoy <em>programming</em> enough to do that for a living. I'm currently studying <em>computer science</em> at the <em>University of Waterloo</em>.</h3>
|
||||
<h3>You can click to see my <a href="/resume.pdf">resume</a>, visit my <a href="https://prose.nsood.in">blog</a>, or check out ways to contact me below.</h3>
|
||||
<div class="contact-methods">
|
||||
<a href="mailto:mail@nsood.in" class="contact-method" aria-label="Email">
|
||||
|
|
144
js/script.js
144
js/script.js
|
@ -3,34 +3,30 @@ const canvas = document.querySelector('canvas#bg');
|
|||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
|
||||
const snowing = false;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
const FILL_STYLES = {
|
||||
light: 'rgba(0,0,0,0.05)',
|
||||
dark: 'rgba(255,255,255,0.15)',
|
||||
snowing: 'rgb(206, 212, 220)'
|
||||
dark: 'rgba(255,255,255,0.15)'
|
||||
};
|
||||
const BACKGROUNDS = {
|
||||
evening: 'linear-gradient(#011d32, #3b3b89)',
|
||||
night: '#000000'
|
||||
};
|
||||
ctx.fillStyle = snowing ? FILL_STYLES.snowing : FILL_STYLES.light;
|
||||
let darkMode = 0;
|
||||
if(snowing) {
|
||||
document.body.classList.add('dark-mode');
|
||||
document.body.style.background = BACKGROUNDS.evening;
|
||||
|
||||
const colorMode = localStorage.getItem('color-mode');
|
||||
|
||||
if(colorMode) {
|
||||
document.body.setAttribute('class', colorMode);
|
||||
}
|
||||
|
||||
addEventListener('resize', () => {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
// need to set ctx.fillStyle whenever I resize???
|
||||
ctx.fillStyle = darkMode ? FILL_STYLES.dark : snowing ? FILL_STYLES.snowing : FILL_STYLES.light;
|
||||
});
|
||||
|
||||
function isDarkMode() {
|
||||
return document.body.classList.contains('dark-mode') ||
|
||||
(document.body.classList.length === 0 && matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
}
|
||||
|
||||
function Point() {
|
||||
const r = snowing ? 4 : 8;
|
||||
const r = 8;
|
||||
|
||||
// progress below 0 is neglected, negative initial
|
||||
// progress serves to introduce random delays -
|
||||
|
@ -44,49 +40,38 @@ function Point() {
|
|||
this.init = function() {
|
||||
this.progress = initialProgress;
|
||||
this.x = Math.random() * canvas.width;
|
||||
this.y = snowing ? initialProgress * canvas.height / 3 : Math.random() * canvas.height;
|
||||
this.r = snowing ? Math.random() * r : 0;
|
||||
this.y = Math.random() * canvas.height;
|
||||
this.r = 0;
|
||||
this.rng = Math.random();
|
||||
}
|
||||
|
||||
this.draw = function() {
|
||||
if(this.progress >= 0) {
|
||||
ctx.fillStyle = isDarkMode() ? FILL_STYLES.dark : FILL_STYLES.light;
|
||||
ctx.beginPath();
|
||||
// radius calculation: maps progress from [0, 1] to [0, pi],
|
||||
// then takes sine of that to get an increase, then decrease
|
||||
// in radius. absolute value to prevent floating point errors
|
||||
// accidentally causing negative sine values which cause ctx.arc
|
||||
// to throw errors
|
||||
if(!snowing) ctx.arc(this.x, this.y, Math.abs(Math.sin(Math.PI*this.progress)*r), 0, 2*Math.PI);
|
||||
else ctx.arc(
|
||||
this.x + 20 * Math.sin(this.progress * 3 * Math.PI) + Math.cos(this.progress * (5*this.rng) * Math.PI),
|
||||
this.y, this.r, 0, 2*Math.PI);
|
||||
ctx.arc(this.x, this.y, Math.abs(Math.sin(Math.PI*this.progress)*r), 0, 2*Math.PI);
|
||||
ctx.fill();
|
||||
}
|
||||
};
|
||||
this.render = function() {
|
||||
if(snowing) {
|
||||
this.y += (Math.pow(this.rng, 0.25)) * 0.5 * (1 + Math.sin(this.progress % Math.PI));
|
||||
this.x = (this.x + 0.3) % canvas.width; // wind
|
||||
this.progress += 0.005;
|
||||
this.draw();
|
||||
if(this.y >= canvas.height + r) this.init();
|
||||
}
|
||||
else {
|
||||
// stars come faster than they go
|
||||
// so user can look at them longer
|
||||
// i guess? idk this just looked pretty
|
||||
if(this.progress > 0.5) this.progress += 0.005;
|
||||
else this.progress += 0.05;
|
||||
this.draw();
|
||||
if(this.progress >= 1) this.init();
|
||||
}
|
||||
// stars come faster than they go
|
||||
// so user can look at them longer
|
||||
// i guess? idk this just looked pretty
|
||||
if(this.progress > 0.5) this.progress += 0.005;
|
||||
else this.progress += 0.05;
|
||||
this.draw();
|
||||
if(this.progress >= 1) this.init();
|
||||
}
|
||||
}
|
||||
|
||||
const dots = [];
|
||||
|
||||
const n = snowing ? 50 : 20;
|
||||
const n = 20;
|
||||
|
||||
for(let i = 0; i < n; i++) {
|
||||
dots[i] = new Point();
|
||||
|
@ -124,62 +109,49 @@ document.querySelector('.age').textContent = `${tens[Math.floor(age / 10)]} ${on
|
|||
// easter egg
|
||||
|
||||
const sequences = [
|
||||
'CLAPOFF'.split('').map(s => 'Key' + s),
|
||||
'CLAPON'.split('').map(s => 'Key' + s)
|
||||
];
|
||||
'DARK',
|
||||
'LITE',
|
||||
'PURP',
|
||||
'MINT',
|
||||
'YELL',
|
||||
'BLUE',
|
||||
'SAVE'
|
||||
].map(seq => {
|
||||
return {
|
||||
word: seq,
|
||||
combo: seq.split('').map(c => 'Key' + c)
|
||||
}
|
||||
});
|
||||
|
||||
let eei = 0;
|
||||
const lastFourKeys = [];
|
||||
|
||||
addEventListener('keypress', e => {
|
||||
const clapper = document.querySelector('.clapper');
|
||||
const sequence = sequences[darkMode];
|
||||
if(e.code == sequence[eei]) {
|
||||
eei++;
|
||||
}
|
||||
else {
|
||||
eei = 0;
|
||||
}
|
||||
|
||||
if(eei == sequence.length) {
|
||||
eei = 0;
|
||||
clapper.classList.toggle('clapping');
|
||||
setTimeout(() => {
|
||||
if(!snowing) {
|
||||
if(darkMode) {
|
||||
document.body.classList.remove('dark-mode');
|
||||
ctx.fillStyle = FILL_STYLES.light;
|
||||
darkMode = 0;
|
||||
}
|
||||
else {
|
||||
document.body.classList.add('dark-mode');
|
||||
ctx.fillStyle = FILL_STYLES.dark;
|
||||
darkMode = 1;
|
||||
}
|
||||
eei = 0;
|
||||
} else {
|
||||
if(darkMode) {
|
||||
ctx.fillStyle = FILL_STYLES.snowing;
|
||||
document.body.style.background = BACKGROUNDS.evening;
|
||||
darkMode = 0;
|
||||
}
|
||||
else {
|
||||
ctx.fillStyle = FILL_STYLES.dark;
|
||||
document.body.style.background = BACKGROUNDS.night;
|
||||
darkMode = 1;
|
||||
lastFourKeys.push(e.code);
|
||||
while(lastFourKeys.length > 4) lastFourKeys.shift();
|
||||
sequences.forEach(({word, combo}) => {
|
||||
if(combo.every((v, i) => v === lastFourKeys[i])) {
|
||||
if(word === 'SAVE') {
|
||||
if(confirm('Would you like to save the current color mode to local browser storage?')) {
|
||||
localStorage.setItem('color-mode', document.body.getAttribute('class'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
setTimeout(() => clapper.classList.toggle('clapping'), 500);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
const clapper = document.querySelector('.clapper');
|
||||
clapper.classList.toggle('clapping');
|
||||
setTimeout(() => {
|
||||
document.body.setAttribute('class', `${word.toLowerCase()}-mode`);
|
||||
setTimeout(() => clapper.classList.toggle('clapping'), 500);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function among() {
|
||||
alert('haha among us');
|
||||
}
|
||||
|
||||
document.querySelector('.among').addEventListener('keypress', e => {
|
||||
if(e.code === 'Space') {
|
||||
e.preventDefault();
|
||||
among();
|
||||
}
|
||||
document.querySelector('.among').addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
among();
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue