Skip to main content
Code Review

Return to Question

Notice removed Draw attention by Aleksandr Hovhannisyan
Bounty Ended with Sᴀᴍ Onᴇᴌᴀ's answer chosen by Aleksandr Hovhannisyan
Tweeted twitter.com/StackCodeReview/status/1146524043933302784
Notice added Draw attention by Aleksandr Hovhannisyan
Bounty Started worth 100 reputation by Aleksandr Hovhannisyan
edited tags
Link
Get rid of outdated line of code from a previous version.
Source Link
var repos = new Map();
setupRepos();
requestRepoData();
/** Defines all repositories of interest, to be displayed in the Projects section of the page in
 * the precise order that they appear here. These serve as filters for when we scour through all 
 * repositories returned by the GitHub API. Though these are mostly hardcoded, we only have to enter
 * the information within this function; the rest of the script is not hardcoded in that respect.
 * Notable downside: if the name of the repo changes for whatever reason, it will need to be updated here.
 */
function setupRepos() {
 addRepo("Scribe-Text-Editor", "Scribe: Text Editor", ["cpp", "qt5", "qtcreator"]);
 addRepo("EmbodyGame", "Embody: Game", ["csharp", "unity", "ai"]);
 addRepo("aleksandrhovhannisyan.github.io", "Personal Website", ["html5", "css", "javascript"]);
 addRepo("Steering-Behaviors", "Steering Behaviors", ["csharp", "unity", "ai"]);
 addRepo("MIPS-Linked-List", "ASM Linked List", ["mips", "asm", "qtspim"]);
 addRepo('Dimension35', "dim35: Game", ["godot", "networking"]);
}
/** Associates the given official name of a repo with an object representing custom data about that repository.
 * This hashing/association makes it easier to do lookups later on.
 * 
 * @param {string} officialName - The unique name used to identify this repository on GitHub.
 * @param {string} customName - A custom name for the repository, not necessarily the same as its official name.
 * @param {string[]} topics - An array of strings denoting the topics that correspond to this repo.
 */
function addRepo(officialName, customName, topics) {
 // Note 1: We define a custom name here for two reasons: 1) some repo names are quite long, such as my website's,
 // and 2) multi-word repos have hyphens instead of spaces on GitHub, so we'd need to replace those (which would be wasteful)
 // Note 2: We define the topics here instead of parsing them dynamically because GitHub's API returns the topics
 // as a *sorted* array, which means we'll end up displaying undesired tags (since we don't show all of them).
 // This approach gives us more control but sacrifices flexibility, since we have to enter topics manually for repos of interest.
 repos.set(officialName, { "customName" : customName, "topics" : topics, "card" : null });
}
/** Convenience wrapper for accessing the custom data for a particular repo. Uses the given
 * repo's official name (per the GitHub API) as the key into the associated Map.
 * 
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Object} The custom object representing the given repo.
 */
function get(repo) {
 // Notice how the underlying syntax is messy; the wrapper makes it cleaner when used
 return repos.get(repo.name);
}
function requestRepoData() {
 let request = new XMLHttpRequest();
 request.open('GET', 'https://api.github.com/users/AleksandrHovhannisyan/repos', true);
 request.onload = parseRepos;
 request.send();
}
function parseRepos() {
 if (this.status !== 200) return;
 let data = JSON.parse(this.response);
 // Even though we have to loop over all repos to find the ones we want, doing so is arguably
 // much faster (and easier) than making separate API requests for each repo of interest
 // Also note that GitHub has a rate limit of 60 requests/hr for unauthenticated IPs
 for (let repo of data) {
 if (repos.has(repo.name)) {
 // We cache the card here instead of publishing it immediately so we can display
 // the cards in our own order, since the requests are processed out of order (b/c of async)
 get(repo).card = createCardFor(repo);
 }
 }
 publishRepoCards();
}
/** Creates a project card for the given repo. A card consists of a header, description,
 * and footer, as well as an invisible link and hidden content to be displayed when the
 * card is hovered over.
 * 
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A DOM element representing a project card for the given repo.
 */
function createCardFor(repo) {
 let card = document.createElement('section');
 card.setAttribute('class', 'project');
 card.appendChild(headerFor(repo));
 card.appendChild(descriptionFor(repo));
 card.appendChild(footerFor(repo));
 card.appendChild(anchorFor(repo));
 card.appendChild(createHoverContent());
 return card;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A header for the given repo, consisting of three key pieces:
 * the repo icon, the repo name, and the repo's rating (stargazers).
 */
function headerFor(repo) {
 var header = document.createElement('header');
 var icon = document.createElement('span');
 icon.setAttribute('class', 'project-icon');
 // The emoji part of the description on GitHub
 icon.textContent = repo.description.substring(0, 3);
 
 var h4 = document.createElement('h4');
 h4.appendChild(icon);
 h4.appendChild(nameLabelFor(repo));
 
 header.appendChild(h4);
 header.appendChild(stargazerLabelFor(repo));
 return header;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A label for the name of the given repo.
 */
function nameLabelFor(repo) {
 var projectName = document.createElement('span');
 projectName.textContent = get(repo).customName;
 return projectName;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A label showing the number of stargazers for the given repo.
 */
function stargazerLabelFor(repo) {
 var projectRating = document.createElement('span');
 
 var starIcon = document.createElement('i');
 starIcon.setAttribute('class', 'fas fa-star filled');
 
 var starCount = document.createElement('span');
 starCount.textContent = ' ' + repo.stargazers_count;
 
 projectRating.setAttribute('class', 'project-rating');
 projectRating.appendChild(starIcon);
 projectRating.appendChild(starCount);
 
 return projectRating;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} An element containing the description of the given repo.
 */
function descriptionFor(repo) {
 var description = document.createElement('p');
 description.setAttribute('class', 'description');
 // Non-emoji part of the description on GitHub
 description.textContent = repo.description.substring(3);
 return description;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A footer for the name of the given repo, consisting of at most
 * three paragraphs denoting the topics associated with that repo.
 */
function footerFor(repo) {
 var footer = document.createElement('footer');
 footer.setAttribute('class', 'topics');
 const numTopicsToShow = 3;
 for(let topic of get(repo).topics) {
 let p = document.createElement('p');
 p.textContent = topic;
 footer.appendChild(p);
 if (footer.childElementCount === numTopicsToShow) break;
 }
 return footer;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} An anchor element whose href is set to the given repo's "real" URL.
 */
function anchorFor(repo) {
 var anchor = document.createElement('a');
 anchor.setAttribute('class', 'container-link');
 anchor.setAttribute('href', repo.html_url);
 anchor.setAttribute('target', '_blank');
 return anchor;
}
function createHoverContent() {
 var hoverContent = document.createElement('div');
 hoverContent.setAttribute('class', 'hover-content');
 
 var boldText = document.createElement('strong');
 boldText.textContent = 'View on GitHub';
 var externalLinkIcon = document.createElement('i');
 externalLinkIcon.setAttribute('class', 'fas fa-external-link-alt');
 
 hoverContent.appendChild(boldText);
 hoverContent.appendChild(externalLinkIcon);
 return hoverContent;
}
function publishRepoCards() {
 const projects = document.getElementById('projects');
 const placeholder = document.getElementById('project-placeholder');
 for (let repo of repos.values()) {
 projects.insertBefore(repo.card, placeholder);
 }
}
/* ============================================
 General top-level styling
 ============================================
*/
* {
 box-sizing: border-box;
}
:root {
 --main-bg-color: white;
 --nav-bg-color: rgb(44, 44, 44);
 --nav-text-color: rgb(179, 177, 177);
 --nav-min-height: 50px;
 --topic-label-bg-color: #e7e7e7;
 --hr-color: rgba(143, 142, 142, 0.2);
 --text-color-normal: black;
 --text-color-emphasis: black;
 --link-color: rgb(39, 83, 133);
 --button-bg-color: rgb(39, 83, 133);
 --button-bg-hover-color: rgb(83, 129, 182);
 --button-text-color: white;
 --button-text-hover-color: white;
 --skill-hover-bg-color: whitesmoke;
 
 --project-card-bg-color: rgb(253, 253, 253);
 --project-card-shadow: 0px 0px 4px 2px rgba(50, 50, 50, 0.4);
 --project-card-shadow-hover: 0px 1px 6px 2px rgba(10, 10, 10, 0.6);
 --project-card-margin: 30px;
 
 --form-bg-color: rgb(255, 255, 255);
 --form-input-margins: 10px;
 --form-max-width: 475px;
 --page-center-percentage: 80%;
 --global-transition-duration: 0.5s;
 --institution-info-border-width: 3px;
}
.night {
 --main-bg-color: rgb(44, 44, 44);
 --nav-bg-color: rgb(10, 10, 10);
 --topic-label-bg-color: #222222;
 
 --hr-color: rgba(255, 255, 255, 0.2);
 --text-color-normal: rgb(179, 177, 177);
 --text-color-emphasis: rgb(202, 202, 202);
 --link-color: rgb(202, 183, 143);
 
 --button-bg-color: rgb(90, 90, 66);
 --button-bg-hover-color: rgb(141, 141, 114);
 --button-text-color: var(--text-color-emphasis);
 --button-text-hover-color: rgb(24, 24, 24);
 
 --skill-hover-bg-color: rgb(66, 66, 66);
 --project-card-bg-color: rgb(54, 54, 54);
 /* The shadows need to be a bit more prominent so they contrast well in dark mode,
 hence the larger values for blur and spread */
 --project-card-shadow: 0 2px 6px 4px rgba(31, 31, 31, 0.9);
 --project-card-shadow-hover: 0px 2px 10px 5px rgba(10, 10, 10, 0.6);
 --form-bg-color: var(--skill-hover-bg-color);
}
#intro {
 margin-bottom: 40px;
}
#about-me, #projects, #skills, #education, #contact { 
 /* So the fixed navbar doesn't cover up any content we scroll to */
 margin-top: calc((var(--nav-min-height) + 20px) * -1);
 padding-top: calc(var(--nav-min-height) + 20px);
}
#about-me, #projects, #skills, #education {
 margin-bottom: 120px;
}
body {
 font-family: Nunito, sans-serif;
 color: var(--text-color-normal);
 background-color: var(--main-bg-color);
 transition: background-color var(--global-transition-duration);
 
 width: var(--page-center-percentage);
 margin-left: auto;
 margin-right: auto;
}
i, h1, h2, h4, strong, em {
 color: var(--text-color-emphasis);
}
.institution-info h4 {
 margin-left: 10px;
 font-weight: normal;
 color: var(--text-color-normal);
}
h1 {
 font-size: 2em;
 margin-block-start: 0.67em;
 margin-block-end: 0.67em;
}
h1, h2 {
 margin-top: 0;
}
a {
 color: var(--link-color);
}
p {
 color: var(--text-color-normal);
}
/* Links an entire parent container, but the parent must be set to relative position */
.container-link {
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 text-decoration: none;
 z-index: 1;
}
/* ============================================
 Buttons, collapsibles, etc.
 ============================================
*/
/* Note: this is an anchor with a class of button */
.button {
 width: 100%;
 height: 40px;
 line-height: 40px;
 text-align: center;
 display: block;
 
 margin-bottom: 10px;
 margin-right: 15px;
 border-radius: 10px;
 
 font-size: 1em;
 font-weight: bold;
 text-decoration: none;
}
.collapsible {
 font-family: Nunito, sans-serif;
 font-size: 1em;
 display: flex;
 align-items: center;
 border: none;
 outline: none;
 width: 100%;
}
.collapsible span {
 text-align: left;
 padding-left: 10px;
 margin-top: 20px;
 margin-bottom: 20px;
}
.button, .collapsible {
 cursor: pointer;
 border: none;
 background-color: var(--button-bg-color);
 transition: var(--global-transition-duration);
}
.button, .button *, .collapsible * {
 color: var(--button-text-color);
}
.button:hover, .collapsible:hover {
 background-color: var(--button-bg-hover-color);
}
/* To get rid of Firefox's dotted lines when these are clicked */
.button::-moz-focus-inner, .collapsible::-moz-focus-inner {
 border: 0;
}
.button:hover, .button:hover *, .collapsible:hover * {
 color: var(--button-text-hover-color);
}
button:focus {
 outline: none;
}
.fa-angle-right, .fa-angle-down {
 margin-left: 10px;
 margin-right: 20px;
 font-size: 1em;
}
@media only screen and (min-width: 400px) {
 
 .main-buttons {
 display: flex;
 }
 .button {
 max-width: 200px; 
 }
}
/* ============================================
 Navigation (+ night mode nightmode-switch)
 ============================================
*/
#topnav .centered-content {
 width: var(--page-center-percentage);
 margin-left: auto;
 margin-right: auto;
 height: var(--nav-min-height);
 display: flex;
 justify-content: space-between;
 align-items: center;
}
#topnav {
 width: 100%;
 min-height: var(--nav-min-height);
 position: fixed;
 left: 0;
 right: 100%;
 top: 0;
 background-color: var(--nav-bg-color);
 /* This is to ensure that it always appears above everything. */
 z-index: 100;
}
#topnav * {
 color: var(--nav-text-color);
}
.nav-links {
 padding: 0;
 list-style-type: none;
 display: none;
 margin-left: 0;
 margin-right: 0;
}
.nav-links li {
 text-align: center;
 margin: 20px auto;
}
.nav-links a {
 text-decoration: none;
 vertical-align: middle;
 transition: var(--global-transition-duration);
}
#topnav .nav-links a:hover {
 text-decoration: underline;
 color: white;
}
.navbar-hamburger {
 font-size: 1.5em;
}
.nightmode-switch-container, .nightmode-switch-container * {
 display: inline-block;
}
.nightmode-switch {
 width: 40px;
 height: 20px;
 line-height: 15px;
 margin-right: 5px;
 background-color: var(--nav-bg-color);
 border: 3px solid var(--nav-text-color);
 border-radius: 100px;
 cursor: pointer;
 transition: var(--global-transition-duration);
}
.nightmode-switch::before {
 content: "";
 display: inline-block;
 vertical-align: middle;
 line-height: normal;
 margin-left: 2px;
 margin-bottom: 2px;
 width: 12px;
 height: 10px;
 
 background-color: var(--nav-text-color);
 border-radius: 50%;
 transition: var(--global-transition-duration);
}
.night .nightmode-switch::before {
 margin-left: 20px;
}
.nav-links.active {
 display: block;
 background-color: var(--nav-bg-color);
 color: var(--nav-text-color);
 
 /* Make the dropdown take up 100% of the viewport width */
 position: absolute;
 left: 0;
 right: 0;
 top: 20px;
}
@media only screen and (min-width: 820px) {
 /* This is the most important part: shows the links next to each other
 Note: .nav-links.active accounts for an edge case where you open the hamburger
 on a small view and then resize the browser so it's larger.
 */
 .nav-links, .nav-links.active {
 margin: 0;
 position: static;
 display: flex;
 flex-direction: row;
 justify-content: flex-end;
 align-items: center;
 font-size: 1.1em;
 }
 .nav-links li {
 margin: 0;
 }
 .nav-links a {
 margin-left: 40px;
 transition: var(--global-transition-duration);
 }
 .navbar-hamburger {
 display: none;
 }
}
/* ============================================
 Page header (intro, about me)
 ============================================
*/
#page-header {
 margin-top: 100px;
 display: grid;
 column-gap: 50px;
 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
#main-cta {
 margin-bottom: 30px;
 font-size: 1.1em;
}
/* ============================================
 Projects/portfolio cards 
 ============================================
*/
#projects {
 display: grid;
 column-gap: 50px;
 row-gap: 50px;
 /* Fill up space as it's made available, with each card being a minimum of 250px */
 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
/* Don't treat the project header as an item/card, keep it on the top row */
#projects h2 {
 grid-row: 1;
 grid-column: 1 / -1;
}
.project {
 /* To ensure that .project-link (see below) is absolute relative to us and not the page */
 position: relative;
 display: grid;
 grid-template-columns: 1;
 /* Header, description, footer, respectively */
 grid-template-rows: max-content 1fr max-content;
 row-gap: 20px;
}
/* All project cards except the placeholder get a background and box shadow */
.project:not(#project-placeholder) {
 background-color: var(--project-card-bg-color);
 box-shadow: var(--project-card-shadow);
 border-radius: 5px;
 transition: all var(--global-transition-duration);
}
/* Apply margins to all project headers except the placeholder's */
.project:not(#project-placeholder) header {
 margin-top: var(--project-card-margin);
 margin-bottom: 0px;
 margin-left: var(--project-card-margin);
 margin-right: var(--project-card-margin);
 display: grid;
 grid-template-areas: "heading heading rating";
}
.project-icon * {
 width: 24px;
 margin-right: 3px;
 display: inline-block;
 vertical-align: middle;
}
.project h4 {
 margin: 0px;
 align-self: center;
 grid-area: heading;
}
.project-rating {
 font-size: 0.85em;
 justify-self: center;
 align-self: center;
 grid-area: rating;
}
.project .description {
 margin-top: 0px;
 margin-bottom: 0px;
 margin-left: var(--project-card-margin);
 margin-right: var(--project-card-margin);
}
/* Displayed when a user hovers over a project card */
.hover-content {
 font-size: 1.2em;
 /* Again, note that .project has position: relative */
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 /* Center the content for the hover layer */
 display: flex;
 flex-direction: column;
 align-items: center;
 justify-content: center;
 /* Opacity = 0 means it's hidden by default */
 opacity: 0;
 background-color: var(--skill-hover-bg-color);
 transition: var(--global-transition-duration) ease;
}
/* Make it clearer which card is hovered over */
.project:hover:not(#project-placeholder) {
 box-shadow: var(--project-card-shadow-hover);
}
/* Transition for the hover content changes its opacity */
.project:hover .hover-content {
 cursor: pointer;
 opacity: 0.92;
}
.fa-external-link-alt {
 margin-top: 20px;
}
.project-name {
 color: var(--link-color);
 text-decoration: none;
}
.topics {
 display: flex;
 flex-wrap: wrap;
 grid-row: 3;
 margin-top: 0px;
 margin-bottom: var(--project-card-margin);
 margin-left: var(--project-card-margin);
 margin-right: var(--project-card-margin);
}
.topics p {
 font-size: 0.9em;
 padding: 5px;
 margin-top: 10px;
 margin-bottom: 0px;
 margin-right: 10px;
 border-radius: 2px;
 background-color: var(--topic-label-bg-color);
 box-shadow: 0 0 2px black;
 transition: background-color var(--global-transition-duration);
}
#project-placeholder {
 display: flex;
 flex-direction: column;
 text-align: center;
 justify-content: center;
}
.github-cta {
 display: inline-block;
 
 font-size: 3em;
 margin-top: 20px;
 text-decoration: none;
 color: black;
}
/* ============================================
 Skills (responsive columns)
 ============================================
*/
#skills {
 display: grid;
 column-gap: 50px;
 row-gap: 20px;
 grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
#skills h2 {
 grid-row: 1;
 grid-column: 1 / -1;
}
.skill-category h4 {
 margin-bottom: 5px;
}
.skill-item {
 margin-top: 10px;
 display: grid;
 column-gap: 10px;
 grid-template-columns: 1fr 1fr;
}
.skill-item:hover {
 background-color: var(--skill-hover-bg-color);
}
.skill-name {
 grid-column: 1;
}
.skill-rating {
 grid-column: 2;
 display: inline;
 text-align: right;
}
.fa-star.filled {
 color: var(--button-bg-color);
}
.fa-star.empty {
 color: var(--nav-text-color);
}
.night .fa-star.filled {
 color: rgb(145, 145, 145);
}
.night .fa-star.empty {
 color: var(--button-bg-color);
}
/* ============================================
 Education (institutions, coursework, etc.)
 ============================================
*/
.institution {
 margin-top: 20px;
}
/* Course and award container */
.institution-info {
 display: grid;
 /* Mobile first: only one column. Changes to two columns on bigger screens. See media query below. */
 grid-template-columns: 1fr;
 /* Will be set to a sufficiently large max-height by corresponding click handler for .collapsible */
 max-height: 0px;
 transition: max-height var(--global-transition-duration);
 overflow: hidden;
 border: solid var(--institution-info-border-width) var(--button-bg-color);
 border-top: none;
}
.institution-info .awards {
 /* Only matters on mobile, where the awards are stacked underneath courses */
 border-top: solid var(--institution-info-border-width) var(--button-bg-color);
}
.institution-info ul {
 padding-right: 10px;
}
.institution-info p {
 padding-left: 10px;
}
/* Line up courses and awards side by side on larger screens */
@media only screen and (min-width: 800px) {
 .institution-info {
 grid-template-rows: 1fr;
 grid-template-columns: auto auto;
 }
 .institution-info .awards {
 /* Now that it's lined up to the right of the courses, there's no need for a top border */
 border-top: none;
 /* But there is for a left border */
 border-left: solid var(--institution-info-border-width) var(--button-bg-color);
 }
}
/* ============================================
 Contact form
 ============================================
*/
#contact {
 display: grid;
 grid-template-areas: "form"
 "socials";
 grid-template-rows: auto;
 column-gap: 50px;
}
#contact-form {
 grid-area: form;
}
#social-networks {
 grid-area: socials;
}
@media only screen and (min-width: 700px) {
 #contact {
 grid-template-areas: "form form form socials";
 }
}
form {
 margin-bottom: 50px;
 margin-top: 30px;
 max-width: var(--form-max-width);
}
form * {
 color: var(--text-color-normal);
 font-family: Nunito, sans-serif;
 font-size: 1em;
}
form input:not([class="button"]), form textarea {
 height: 30px;
 width: 100%;
 margin-bottom: 15px;
 padding: 10px;
 
 background-color: var(--form-bg-color);
 border: 0px solid;
 box-shadow: 0 0 3px 1px rgb(172, 172, 172);
 border-radius: 3px;
 transition: var(--global-transition-duration);
}
form label {
 margin-bottom: 5px;
 display: block;
}
form input:focus, form textarea:focus {
 outline: none;
 box-shadow: 0 0 5px 2px rgb(155, 155, 155);
}
form textarea {
 max-width: var(--form-max-width);
 min-height: 200px;
 transition: height 0s;
 transition: background-color var(--global-transition-duration);
}
form .button {
 max-width: 100%;
 width: 100%;
 height: 45px;
}
/* Yum, honey */
input.honeypot {
 display: none;
}
/* ============================================
 Social networks
 ============================================
*/
#social-networks {
 display: grid;
 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
 grid-template-rows: min-content;
 grid-auto-rows: min-content;
 row-gap: 50px;
 column-gap: 30px;
 margin-bottom: 50px;
}
#social-networks h3 {
 grid-row: 1;
 grid-column: 1 / -1;
}
.social-network {
 /* Position relative because we have an absolutely 
 positioned .container-link as a child */
 position: relative;
 display: grid;
 grid-template-columns: auto 1fr;
 column-gap: 20px;
}
.social-network:hover {
 cursor: pointer;
 background-color: var(--skill-hover-bg-color);
}
.social-network .fa-stack {
 grid-column: 1;
 display: grid;
}
.fa-stack i {
 align-self: center;
 justify-self: center;
}
/* Whatever icon is being used as the background one */
.fa-stack-2x {
 opacity: 0;
 font-size: 1.5em;
 color: white;
}
.night .fa-stack-2x {
 opacity: 1;
}
.social-network .network-name {
 grid-column: 2;
 align-self: center;
}
#social-networks .fa-linkedin {
 color: #0077B5;
}
#social-networks .fa-github {
 color: black;
}
#social-networks .fa-stack-exchange {
 color: #195398;
}
#social-networks .fa-address-book {
 color: #37A000;
}
#page-footer {
 position: absolute;
 left: 0;
 height: 50px;
 width: 100%;
 background: var(--nav-bg-color);
 color: var(--nav-text-color);
 display: flex;
 justify-content: center;
 align-items: center;
}
<!DOCTYPE html>
<html lang="en">
 <head>
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <!-- Nunito font looks amazing :) -->
 <link href="https://fonts.googleapis.com/css?family=Nunito&display=swap" rel="stylesheet">
 <!-- Font Awesome icons -->
 <script src="https://kit.fontawesome.com/7d7dc6ad85.js"></script>
 <!-- Custom stylesheet -->
 <link rel="stylesheet" href="style.css">
 <!-- Favicon -->
 <link rel="icon" href="favicon.ico" type='image/x-icon'>
 <!-- Preview image (e.g., for LinkedIn or Facebook) -->
 <meta property="og:image" content="https://avatars2.githubusercontent.com/u/19352442?s=400&amp;v=4">
 <title>Aleksandr Hovhannisyan</title>
 <!-- Contact form -->
 <meta name="referrer" content="origin">
 </head>
 <body>
 <nav id="topnav">
 <div class="centered-content">
 <div class="nightmode-switch-container">
 <div class="nightmode-switch"></div><span>Light mode</span>
 </div>
 <i class="navbar-hamburger fas fa-bars"></i>
 <ul class="nav-links">
 <li><a href="#about-me">About Me</a></li>
 <li><a href="#projects">Projects</a></li>
 <li><a href="#skills">Skills</a></li>
 <li><a href="#education">Education</a></li>
 <li><a href="#contact">Contact</a></li>
 </ul>
 </div>
 </nav>
 <article id="content">
 
 <section id="projects">
 <h2>Projects &#128193;</h2>
 <aside id="project-placeholder" class="project">
 <header>
 <h4>Want to see more of my work?</h4>
 </header>
 <div>
 <p>Check out my other repos:</p>
 <a class="github-cta" href="https://github.com/AleksandrHovhannisyan?tab=repositories" target="_blank"><i class="fab fa-github"></i></a>
 </div>
 </aside>
 </section>
 </article>
 <!-- Custom javascript -->
 <script src="index.js"></script>
 </body>
</html>
var repos = new Map();
setupRepos();
requestRepoData();
/** Defines all repositories of interest, to be displayed in the Projects section of the page in
 * the precise order that they appear here. These serve as filters for when we scour through all 
 * repositories returned by the GitHub API. Though these are mostly hardcoded, we only have to enter
 * the information within this function; the rest of the script is not hardcoded in that respect.
 * Notable downside: if the name of the repo changes for whatever reason, it will need to be updated here.
 */
function setupRepos() {
 addRepo("Scribe-Text-Editor", "Scribe: Text Editor", ["cpp", "qt5", "qtcreator"]);
 addRepo("EmbodyGame", "Embody: Game", ["csharp", "unity", "ai"]);
 addRepo("aleksandrhovhannisyan.github.io", "Personal Website", ["html5", "css", "javascript"]);
 addRepo("Steering-Behaviors", "Steering Behaviors", ["csharp", "unity", "ai"]);
 addRepo("MIPS-Linked-List", "ASM Linked List", ["mips", "asm", "qtspim"]);
 addRepo('Dimension35', "dim35: Game", ["godot", "networking"]);
}
/** Associates the given official name of a repo with an object representing custom data about that repository.
 * This hashing/association makes it easier to do lookups later on.
 * 
 * @param {string} officialName - The unique name used to identify this repository on GitHub.
 * @param {string} customName - A custom name for the repository, not necessarily the same as its official name.
 * @param {string[]} topics - An array of strings denoting the topics that correspond to this repo.
 */
function addRepo(officialName, customName, topics) {
 // Note 1: We define a custom name here for two reasons: 1) some repo names are quite long, such as my website's,
 // and 2) multi-word repos have hyphens instead of spaces on GitHub, so we'd need to replace those (which would be wasteful)
 // Note 2: We define the topics here instead of parsing them dynamically because GitHub's API returns the topics
 // as a *sorted* array, which means we'll end up displaying undesired tags (since we don't show all of them).
 // This approach gives us more control but sacrifices flexibility, since we have to enter topics manually for repos of interest.
 repos.set(officialName, { "customName" : customName, "topics" : topics, "card" : null });
}
/** Convenience wrapper for accessing the custom data for a particular repo. Uses the given
 * repo's official name (per the GitHub API) as the key into the associated Map.
 * 
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Object} The custom object representing the given repo.
 */
function get(repo) {
 // Notice how the underlying syntax is messy; the wrapper makes it cleaner when used
 return repos.get(repo.name);
}
function requestRepoData() {
 let request = new XMLHttpRequest();
 request.open('GET', 'https://api.github.com/users/AleksandrHovhannisyan/repos', true);
 request.onload = parseRepos;
 request.send();
}
function parseRepos() {
 if (this.status !== 200) return;
 let data = JSON.parse(this.response);
 // Even though we have to loop over all repos to find the ones we want, doing so is arguably
 // much faster (and easier) than making separate API requests for each repo of interest
 // Also note that GitHub has a rate limit of 60 requests/hr for unauthenticated IPs
 for (let repo of data) {
 if (repos.has(repo.name)) {
 // We cache the card here instead of publishing it immediately so we can display
 // the cards in our own order, since the requests are processed out of order (b/c of async)
 get(repo).card = createCardFor(repo);
 }
 }
 publishRepoCards();
}
/** Creates a project card for the given repo. A card consists of a header, description,
 * and footer, as well as an invisible link and hidden content to be displayed when the
 * card is hovered over.
 * 
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A DOM element representing a project card for the given repo.
 */
function createCardFor(repo) {
 let card = document.createElement('section');
 card.setAttribute('class', 'project');
 card.appendChild(headerFor(repo));
 card.appendChild(descriptionFor(repo));
 card.appendChild(footerFor(repo));
 card.appendChild(anchorFor(repo));
 card.appendChild(createHoverContent());
 return card;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A header for the given repo, consisting of three key pieces:
 * the repo icon, the repo name, and the repo's rating (stargazers).
 */
function headerFor(repo) {
 var header = document.createElement('header');
 var icon = document.createElement('span');
 icon.setAttribute('class', 'project-icon');
 // The emoji part of the description on GitHub
 icon.textContent = repo.description.substring(0, 3);
 
 var h4 = document.createElement('h4');
 h4.appendChild(icon);
 h4.appendChild(nameLabelFor(repo));
 
 header.appendChild(h4);
 header.appendChild(stargazerLabelFor(repo));
 return header;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A label for the name of the given repo.
 */
function nameLabelFor(repo) {
 var projectName = document.createElement('span');
 projectName.textContent = get(repo).customName;
 return projectName;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A label showing the number of stargazers for the given repo.
 */
function stargazerLabelFor(repo) {
 var projectRating = document.createElement('span');
 
 var starIcon = document.createElement('i');
 starIcon.setAttribute('class', 'fas fa-star filled');
 
 var starCount = document.createElement('span');
 starCount.textContent = ' ' + repo.stargazers_count;
 
 projectRating.setAttribute('class', 'project-rating');
 projectRating.appendChild(starIcon);
 projectRating.appendChild(starCount);
 
 return projectRating;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} An element containing the description of the given repo.
 */
function descriptionFor(repo) {
 var description = document.createElement('p');
 description.setAttribute('class', 'description');
 // Non-emoji part of the description on GitHub
 description.textContent = repo.description.substring(3);
 return description;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A footer for the name of the given repo, consisting of at most
 * three paragraphs denoting the topics associated with that repo.
 */
function footerFor(repo) {
 var footer = document.createElement('footer');
 footer.setAttribute('class', 'topics');
 const numTopicsToShow = 3;
 for(let topic of get(repo).topics) {
 let p = document.createElement('p');
 p.textContent = topic;
 footer.appendChild(p);
 if (footer.childElementCount === numTopicsToShow) break;
 }
 return footer;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} An anchor element whose href is set to the given repo's "real" URL.
 */
function anchorFor(repo) {
 var anchor = document.createElement('a');
 anchor.setAttribute('class', 'container-link');
 anchor.setAttribute('href', repo.html_url);
 anchor.setAttribute('target', '_blank');
 return anchor;
}
function createHoverContent() {
 var hoverContent = document.createElement('div');
 hoverContent.setAttribute('class', 'hover-content');
 
 var boldText = document.createElement('strong');
 boldText.textContent = 'View on GitHub';
 var externalLinkIcon = document.createElement('i');
 externalLinkIcon.setAttribute('class', 'fas fa-external-link-alt');
 
 hoverContent.appendChild(boldText);
 hoverContent.appendChild(externalLinkIcon);
 return hoverContent;
}
function publishRepoCards() {
 const projects = document.getElementById('projects');
 const placeholder = document.getElementById('project-placeholder');
 for (let repo of repos.values()) {
 projects.insertBefore(repo.card, placeholder);
 }
}
/* ============================================
 General top-level styling
 ============================================
*/
* {
 box-sizing: border-box;
}
:root {
 --main-bg-color: white;
 --nav-bg-color: rgb(44, 44, 44);
 --nav-text-color: rgb(179, 177, 177);
 --nav-min-height: 50px;
 --topic-label-bg-color: #e7e7e7;
 --hr-color: rgba(143, 142, 142, 0.2);
 --text-color-normal: black;
 --text-color-emphasis: black;
 --link-color: rgb(39, 83, 133);
 --button-bg-color: rgb(39, 83, 133);
 --button-bg-hover-color: rgb(83, 129, 182);
 --button-text-color: white;
 --button-text-hover-color: white;
 --skill-hover-bg-color: whitesmoke;
 
 --project-card-bg-color: rgb(253, 253, 253);
 --project-card-shadow: 0px 0px 4px 2px rgba(50, 50, 50, 0.4);
 --project-card-shadow-hover: 0px 1px 6px 2px rgba(10, 10, 10, 0.6);
 --project-card-margin: 30px;
 
 --form-bg-color: rgb(255, 255, 255);
 --form-input-margins: 10px;
 --form-max-width: 475px;
 --page-center-percentage: 80%;
 --global-transition-duration: 0.5s;
 --institution-info-border-width: 3px;
}
.night {
 --main-bg-color: rgb(44, 44, 44);
 --nav-bg-color: rgb(10, 10, 10);
 --topic-label-bg-color: #222222;
 
 --hr-color: rgba(255, 255, 255, 0.2);
 --text-color-normal: rgb(179, 177, 177);
 --text-color-emphasis: rgb(202, 202, 202);
 --link-color: rgb(202, 183, 143);
 
 --button-bg-color: rgb(90, 90, 66);
 --button-bg-hover-color: rgb(141, 141, 114);
 --button-text-color: var(--text-color-emphasis);
 --button-text-hover-color: rgb(24, 24, 24);
 
 --skill-hover-bg-color: rgb(66, 66, 66);
 --project-card-bg-color: rgb(54, 54, 54);
 /* The shadows need to be a bit more prominent so they contrast well in dark mode,
 hence the larger values for blur and spread */
 --project-card-shadow: 0 2px 6px 4px rgba(31, 31, 31, 0.9);
 --project-card-shadow-hover: 0px 2px 10px 5px rgba(10, 10, 10, 0.6);
 --form-bg-color: var(--skill-hover-bg-color);
}
#intro {
 margin-bottom: 40px;
}
#about-me, #projects, #skills, #education, #contact { 
 /* So the fixed navbar doesn't cover up any content we scroll to */
 margin-top: calc((var(--nav-min-height) + 20px) * -1);
 padding-top: calc(var(--nav-min-height) + 20px);
}
#about-me, #projects, #skills, #education {
 margin-bottom: 120px;
}
body {
 font-family: Nunito, sans-serif;
 color: var(--text-color-normal);
 background-color: var(--main-bg-color);
 transition: background-color var(--global-transition-duration);
 
 width: var(--page-center-percentage);
 margin-left: auto;
 margin-right: auto;
}
i, h1, h2, h4, strong, em {
 color: var(--text-color-emphasis);
}
.institution-info h4 {
 margin-left: 10px;
 font-weight: normal;
 color: var(--text-color-normal);
}
h1 {
 font-size: 2em;
 margin-block-start: 0.67em;
 margin-block-end: 0.67em;
}
h1, h2 {
 margin-top: 0;
}
a {
 color: var(--link-color);
}
p {
 color: var(--text-color-normal);
}
/* Links an entire parent container, but the parent must be set to relative position */
.container-link {
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 text-decoration: none;
 z-index: 1;
}
/* ============================================
 Buttons, collapsibles, etc.
 ============================================
*/
/* Note: this is an anchor with a class of button */
.button {
 width: 100%;
 height: 40px;
 line-height: 40px;
 text-align: center;
 display: block;
 
 margin-bottom: 10px;
 margin-right: 15px;
 border-radius: 10px;
 
 font-size: 1em;
 font-weight: bold;
 text-decoration: none;
}
.collapsible {
 font-family: Nunito, sans-serif;
 font-size: 1em;
 display: flex;
 align-items: center;
 border: none;
 outline: none;
 width: 100%;
}
.collapsible span {
 text-align: left;
 padding-left: 10px;
 margin-top: 20px;
 margin-bottom: 20px;
}
.button, .collapsible {
 cursor: pointer;
 border: none;
 background-color: var(--button-bg-color);
 transition: var(--global-transition-duration);
}
.button, .button *, .collapsible * {
 color: var(--button-text-color);
}
.button:hover, .collapsible:hover {
 background-color: var(--button-bg-hover-color);
}
/* To get rid of Firefox's dotted lines when these are clicked */
.button::-moz-focus-inner, .collapsible::-moz-focus-inner {
 border: 0;
}
.button:hover, .button:hover *, .collapsible:hover * {
 color: var(--button-text-hover-color);
}
button:focus {
 outline: none;
}
.fa-angle-right, .fa-angle-down {
 margin-left: 10px;
 margin-right: 20px;
 font-size: 1em;
}
@media only screen and (min-width: 400px) {
 
 .main-buttons {
 display: flex;
 }
 .button {
 max-width: 200px; 
 }
}
/* ============================================
 Navigation (+ night mode nightmode-switch)
 ============================================
*/
#topnav .centered-content {
 width: var(--page-center-percentage);
 margin-left: auto;
 margin-right: auto;
 height: var(--nav-min-height);
 display: flex;
 justify-content: space-between;
 align-items: center;
}
#topnav {
 width: 100%;
 min-height: var(--nav-min-height);
 position: fixed;
 left: 0;
 right: 100%;
 top: 0;
 background-color: var(--nav-bg-color);
 /* This is to ensure that it always appears above everything. */
 z-index: 100;
}
#topnav * {
 color: var(--nav-text-color);
}
.nav-links {
 padding: 0;
 list-style-type: none;
 display: none;
 margin-left: 0;
 margin-right: 0;
}
.nav-links li {
 text-align: center;
 margin: 20px auto;
}
.nav-links a {
 text-decoration: none;
 vertical-align: middle;
 transition: var(--global-transition-duration);
}
#topnav .nav-links a:hover {
 text-decoration: underline;
 color: white;
}
.navbar-hamburger {
 font-size: 1.5em;
}
.nightmode-switch-container, .nightmode-switch-container * {
 display: inline-block;
}
.nightmode-switch {
 width: 40px;
 height: 20px;
 line-height: 15px;
 margin-right: 5px;
 background-color: var(--nav-bg-color);
 border: 3px solid var(--nav-text-color);
 border-radius: 100px;
 cursor: pointer;
 transition: var(--global-transition-duration);
}
.nightmode-switch::before {
 content: "";
 display: inline-block;
 vertical-align: middle;
 line-height: normal;
 margin-left: 2px;
 margin-bottom: 2px;
 width: 12px;
 height: 10px;
 
 background-color: var(--nav-text-color);
 border-radius: 50%;
 transition: var(--global-transition-duration);
}
.night .nightmode-switch::before {
 margin-left: 20px;
}
.nav-links.active {
 display: block;
 background-color: var(--nav-bg-color);
 color: var(--nav-text-color);
 
 /* Make the dropdown take up 100% of the viewport width */
 position: absolute;
 left: 0;
 right: 0;
 top: 20px;
}
@media only screen and (min-width: 820px) {
 /* This is the most important part: shows the links next to each other
 Note: .nav-links.active accounts for an edge case where you open the hamburger
 on a small view and then resize the browser so it's larger.
 */
 .nav-links, .nav-links.active {
 margin: 0;
 position: static;
 display: flex;
 flex-direction: row;
 justify-content: flex-end;
 align-items: center;
 font-size: 1.1em;
 }
 .nav-links li {
 margin: 0;
 }
 .nav-links a {
 margin-left: 40px;
 transition: var(--global-transition-duration);
 }
 .navbar-hamburger {
 display: none;
 }
}
/* ============================================
 Page header (intro, about me)
 ============================================
*/
#page-header {
 margin-top: 100px;
 display: grid;
 column-gap: 50px;
 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
#main-cta {
 margin-bottom: 30px;
 font-size: 1.1em;
}
/* ============================================
 Projects/portfolio cards 
 ============================================
*/
#projects {
 display: grid;
 column-gap: 50px;
 row-gap: 50px;
 /* Fill up space as it's made available, with each card being a minimum of 250px */
 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
/* Don't treat the project header as an item/card, keep it on the top row */
#projects h2 {
 grid-row: 1;
 grid-column: 1 / -1;
}
.project {
 /* To ensure that .project-link (see below) is absolute relative to us and not the page */
 position: relative;
 display: grid;
 grid-template-columns: 1;
 /* Header, description, footer, respectively */
 grid-template-rows: max-content 1fr max-content;
 row-gap: 20px;
}
/* All project cards except the placeholder get a background and box shadow */
.project:not(#project-placeholder) {
 background-color: var(--project-card-bg-color);
 box-shadow: var(--project-card-shadow);
 border-radius: 5px;
 transition: all var(--global-transition-duration);
}
/* Apply margins to all project headers except the placeholder's */
.project:not(#project-placeholder) header {
 margin-top: var(--project-card-margin);
 margin-bottom: 0px;
 margin-left: var(--project-card-margin);
 margin-right: var(--project-card-margin);
 display: grid;
 grid-template-areas: "heading heading rating";
}
.project-icon * {
 width: 24px;
 margin-right: 3px;
 display: inline-block;
 vertical-align: middle;
}
.project h4 {
 margin: 0px;
 align-self: center;
 grid-area: heading;
}
.project-rating {
 font-size: 0.85em;
 justify-self: center;
 align-self: center;
 grid-area: rating;
}
.project .description {
 margin-top: 0px;
 margin-bottom: 0px;
 margin-left: var(--project-card-margin);
 margin-right: var(--project-card-margin);
}
/* Displayed when a user hovers over a project card */
.hover-content {
 font-size: 1.2em;
 /* Again, note that .project has position: relative */
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 /* Center the content for the hover layer */
 display: flex;
 flex-direction: column;
 align-items: center;
 justify-content: center;
 /* Opacity = 0 means it's hidden by default */
 opacity: 0;
 background-color: var(--skill-hover-bg-color);
 transition: var(--global-transition-duration) ease;
}
/* Make it clearer which card is hovered over */
.project:hover:not(#project-placeholder) {
 box-shadow: var(--project-card-shadow-hover);
}
/* Transition for the hover content changes its opacity */
.project:hover .hover-content {
 cursor: pointer;
 opacity: 0.92;
}
.fa-external-link-alt {
 margin-top: 20px;
}
.project-name {
 color: var(--link-color);
 text-decoration: none;
}
.topics {
 display: flex;
 flex-wrap: wrap;
 grid-row: 3;
 margin-top: 0px;
 margin-bottom: var(--project-card-margin);
 margin-left: var(--project-card-margin);
 margin-right: var(--project-card-margin);
}
.topics p {
 font-size: 0.9em;
 padding: 5px;
 margin-top: 10px;
 margin-bottom: 0px;
 margin-right: 10px;
 border-radius: 2px;
 background-color: var(--topic-label-bg-color);
 box-shadow: 0 0 2px black;
 transition: background-color var(--global-transition-duration);
}
#project-placeholder {
 display: flex;
 flex-direction: column;
 text-align: center;
 justify-content: center;
}
.github-cta {
 display: inline-block;
 
 font-size: 3em;
 margin-top: 20px;
 text-decoration: none;
 color: black;
}
/* ============================================
 Skills (responsive columns)
 ============================================
*/
#skills {
 display: grid;
 column-gap: 50px;
 row-gap: 20px;
 grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
#skills h2 {
 grid-row: 1;
 grid-column: 1 / -1;
}
.skill-category h4 {
 margin-bottom: 5px;
}
.skill-item {
 margin-top: 10px;
 display: grid;
 column-gap: 10px;
 grid-template-columns: 1fr 1fr;
}
.skill-item:hover {
 background-color: var(--skill-hover-bg-color);
}
.skill-name {
 grid-column: 1;
}
.skill-rating {
 grid-column: 2;
 display: inline;
 text-align: right;
}
.fa-star.filled {
 color: var(--button-bg-color);
}
.fa-star.empty {
 color: var(--nav-text-color);
}
.night .fa-star.filled {
 color: rgb(145, 145, 145);
}
.night .fa-star.empty {
 color: var(--button-bg-color);
}
/* ============================================
 Education (institutions, coursework, etc.)
 ============================================
*/
.institution {
 margin-top: 20px;
}
/* Course and award container */
.institution-info {
 display: grid;
 /* Mobile first: only one column. Changes to two columns on bigger screens. See media query below. */
 grid-template-columns: 1fr;
 /* Will be set to a sufficiently large max-height by corresponding click handler for .collapsible */
 max-height: 0px;
 transition: max-height var(--global-transition-duration);
 overflow: hidden;
 border: solid var(--institution-info-border-width) var(--button-bg-color);
 border-top: none;
}
.institution-info .awards {
 /* Only matters on mobile, where the awards are stacked underneath courses */
 border-top: solid var(--institution-info-border-width) var(--button-bg-color);
}
.institution-info ul {
 padding-right: 10px;
}
.institution-info p {
 padding-left: 10px;
}
/* Line up courses and awards side by side on larger screens */
@media only screen and (min-width: 800px) {
 .institution-info {
 grid-template-rows: 1fr;
 grid-template-columns: auto auto;
 }
 .institution-info .awards {
 /* Now that it's lined up to the right of the courses, there's no need for a top border */
 border-top: none;
 /* But there is for a left border */
 border-left: solid var(--institution-info-border-width) var(--button-bg-color);
 }
}
/* ============================================
 Contact form
 ============================================
*/
#contact {
 display: grid;
 grid-template-areas: "form"
 "socials";
 grid-template-rows: auto;
 column-gap: 50px;
}
#contact-form {
 grid-area: form;
}
#social-networks {
 grid-area: socials;
}
@media only screen and (min-width: 700px) {
 #contact {
 grid-template-areas: "form form form socials";
 }
}
form {
 margin-bottom: 50px;
 margin-top: 30px;
 max-width: var(--form-max-width);
}
form * {
 color: var(--text-color-normal);
 font-family: Nunito, sans-serif;
 font-size: 1em;
}
form input:not([class="button"]), form textarea {
 height: 30px;
 width: 100%;
 margin-bottom: 15px;
 padding: 10px;
 
 background-color: var(--form-bg-color);
 border: 0px solid;
 box-shadow: 0 0 3px 1px rgb(172, 172, 172);
 border-radius: 3px;
 transition: var(--global-transition-duration);
}
form label {
 margin-bottom: 5px;
 display: block;
}
form input:focus, form textarea:focus {
 outline: none;
 box-shadow: 0 0 5px 2px rgb(155, 155, 155);
}
form textarea {
 max-width: var(--form-max-width);
 min-height: 200px;
 transition: height 0s;
 transition: background-color var(--global-transition-duration);
}
form .button {
 max-width: 100%;
 width: 100%;
 height: 45px;
}
/* Yum, honey */
input.honeypot {
 display: none;
}
/* ============================================
 Social networks
 ============================================
*/
#social-networks {
 display: grid;
 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
 grid-template-rows: min-content;
 grid-auto-rows: min-content;
 row-gap: 50px;
 column-gap: 30px;
 margin-bottom: 50px;
}
#social-networks h3 {
 grid-row: 1;
 grid-column: 1 / -1;
}
.social-network {
 /* Position relative because we have an absolutely 
 positioned .container-link as a child */
 position: relative;
 display: grid;
 grid-template-columns: auto 1fr;
 column-gap: 20px;
}
.social-network:hover {
 cursor: pointer;
 background-color: var(--skill-hover-bg-color);
}
.social-network .fa-stack {
 grid-column: 1;
 display: grid;
}
.fa-stack i {
 align-self: center;
 justify-self: center;
}
/* Whatever icon is being used as the background one */
.fa-stack-2x {
 opacity: 0;
 font-size: 1.5em;
 color: white;
}
.night .fa-stack-2x {
 opacity: 1;
}
.social-network .network-name {
 grid-column: 2;
 align-self: center;
}
#social-networks .fa-linkedin {
 color: #0077B5;
}
#social-networks .fa-github {
 color: black;
}
#social-networks .fa-stack-exchange {
 color: #195398;
}
#social-networks .fa-address-book {
 color: #37A000;
}
#page-footer {
 position: absolute;
 left: 0;
 height: 50px;
 width: 100%;
 background: var(--nav-bg-color);
 color: var(--nav-text-color);
 display: flex;
 justify-content: center;
 align-items: center;
}
<!DOCTYPE html>
<html lang="en">
 <head>
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <!-- Nunito font looks amazing :) -->
 <link href="https://fonts.googleapis.com/css?family=Nunito&display=swap" rel="stylesheet">
 <!-- Font Awesome icons -->
 <script src="https://kit.fontawesome.com/7d7dc6ad85.js"></script>
 <!-- Custom stylesheet -->
 <link rel="stylesheet" href="style.css">
 <!-- Favicon -->
 <link rel="icon" href="favicon.ico" type='image/x-icon'>
 <!-- Preview image (e.g., for LinkedIn or Facebook) -->
 <meta property="og:image" content="https://avatars2.githubusercontent.com/u/19352442?s=400&amp;v=4">
 <title>Aleksandr Hovhannisyan</title>
 <!-- Contact form -->
 <meta name="referrer" content="origin">
 </head>
 <body>
 <nav id="topnav">
 <div class="centered-content">
 <div class="nightmode-switch-container">
 <div class="nightmode-switch"></div><span>Light mode</span>
 </div>
 <i class="navbar-hamburger fas fa-bars"></i>
 <ul class="nav-links">
 <li><a href="#about-me">About Me</a></li>
 <li><a href="#projects">Projects</a></li>
 <li><a href="#skills">Skills</a></li>
 <li><a href="#education">Education</a></li>
 <li><a href="#contact">Contact</a></li>
 </ul>
 </div>
 </nav>
 <article id="content">
 
 <section id="projects">
 <h2>Projects &#128193;</h2>
 <aside id="project-placeholder" class="project">
 <header>
 <h4>Want to see more of my work?</h4>
 </header>
 <div>
 <p>Check out my other repos:</p>
 <a class="github-cta" href="https://github.com/AleksandrHovhannisyan?tab=repositories" target="_blank"><i class="fab fa-github"></i></a>
 </div>
 </aside>
 </section>
 </article>
 <!-- Custom javascript -->
 <script src="index.js"></script>
 </body>
</html>
var repos = new Map();
setupRepos();
requestRepoData();
/** Defines all repositories of interest, to be displayed in the Projects section of the page in
 * the precise order that they appear here. These serve as filters for when we scour through all 
 * repositories returned by the GitHub API. Though these are mostly hardcoded, we only have to enter
 * the information within this function; the rest of the script is not hardcoded in that respect.
 * Notable downside: if the name of the repo changes for whatever reason, it will need to be updated here.
 */
function setupRepos() {
 addRepo("Scribe-Text-Editor", "Scribe: Text Editor", ["cpp", "qt5", "qtcreator"]);
 addRepo("EmbodyGame", "Embody: Game", ["csharp", "unity", "ai"]);
 addRepo("aleksandrhovhannisyan.github.io", "Personal Website", ["html5", "css", "javascript"]);
 addRepo("Steering-Behaviors", "Steering Behaviors", ["csharp", "unity", "ai"]);
 addRepo("MIPS-Linked-List", "ASM Linked List", ["mips", "asm", "qtspim"]);
 addRepo('Dimension35', "dim35: Game", ["godot", "networking"]);
}
/** Associates the given official name of a repo with an object representing custom data about that repository.
 * This hashing/association makes it easier to do lookups later on.
 * 
 * @param {string} officialName - The unique name used to identify this repository on GitHub.
 * @param {string} customName - A custom name for the repository, not necessarily the same as its official name.
 * @param {string[]} topics - An array of strings denoting the topics that correspond to this repo.
 */
function addRepo(officialName, customName, topics) {
 // Note 1: We define a custom name here for two reasons: 1) some repo names are quite long, such as my website's,
 // and 2) multi-word repos have hyphens instead of spaces on GitHub, so we'd need to replace those (which would be wasteful)
 // Note 2: We define the topics here instead of parsing them dynamically because GitHub's API returns the topics
 // as a *sorted* array, which means we'll end up displaying undesired tags (since we don't show all of them).
 // This approach gives us more control but sacrifices flexibility, since we have to enter topics manually for repos of interest.
 repos.set(officialName, { "customName" : customName, "topics" : topics, "card" : null });
}
/** Convenience wrapper for accessing the custom data for a particular repo. Uses the given
 * repo's official name (per the GitHub API) as the key into the associated Map.
 * 
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Object} The custom object representing the given repo.
 */
function get(repo) {
 // Notice how the underlying syntax is messy; the wrapper makes it cleaner when used
 return repos.get(repo.name);
}
function requestRepoData() {
 let request = new XMLHttpRequest();
 request.open('GET', 'https://api.github.com/users/AleksandrHovhannisyan/repos', true);
 request.onload = parseRepos;
 request.send();
}
function parseRepos() {
 if (this.status !== 200) return;
 let data = JSON.parse(this.response);
 // Even though we have to loop over all repos to find the ones we want, doing so is arguably
 // much faster (and easier) than making separate API requests for each repo of interest
 // Also note that GitHub has a rate limit of 60 requests/hr for unauthenticated IPs
 for (let repo of data) {
 if (repos.has(repo.name)) {
 // We cache the card here instead of publishing it immediately so we can display
 // the cards in our own order, since the requests are processed out of order (b/c of async)
 get(repo).card = createCardFor(repo);
 }
 }
 publishRepoCards();
}
/** Creates a project card for the given repo. A card consists of a header, description,
 * and footer, as well as an invisible link and hidden content to be displayed when the
 * card is hovered over.
 * 
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A DOM element representing a project card for the given repo.
 */
function createCardFor(repo) {
 let card = document.createElement('section');
 card.setAttribute('class', 'project');
 card.appendChild(headerFor(repo));
 card.appendChild(descriptionFor(repo));
 card.appendChild(footerFor(repo));
 card.appendChild(anchorFor(repo));
 card.appendChild(createHoverContent());
 return card;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A header for the given repo, consisting of three key pieces:
 * the repo icon, the repo name, and the repo's rating (stargazers).
 */
function headerFor(repo) {
 var header = document.createElement('header');
 var icon = document.createElement('span');
 icon.setAttribute('class', 'project-icon');
 // The emoji part of the description on GitHub
 icon.textContent = repo.description.substring(0, 3);
 
 var h4 = document.createElement('h4');
 h4.appendChild(icon);
 h4.appendChild(nameLabelFor(repo));
 
 header.appendChild(h4);
 header.appendChild(stargazerLabelFor(repo));
 return header;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A label for the name of the given repo.
 */
function nameLabelFor(repo) {
 var projectName = document.createElement('span');
 projectName.textContent = get(repo).customName;
 return projectName;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A label showing the number of stargazers for the given repo.
 */
function stargazerLabelFor(repo) {
 var projectRating = document.createElement('span');
 
 var starIcon = document.createElement('i');
 starIcon.setAttribute('class', 'fas fa-star filled');
 
 var starCount = document.createElement('span');
 starCount.textContent = ' ' + repo.stargazers_count;
 
 projectRating.setAttribute('class', 'project-rating');
 projectRating.appendChild(starIcon);
 projectRating.appendChild(starCount);
 
 return projectRating;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} An element containing the description of the given repo.
 */
function descriptionFor(repo) {
 var description = document.createElement('p');
 description.setAttribute('class', 'description');
 // Non-emoji part of the description on GitHub
 description.textContent = repo.description.substring(3);
 return description;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} A footer for the name of the given repo, consisting of at most
 * three paragraphs denoting the topics associated with that repo.
 */
function footerFor(repo) {
 var footer = document.createElement('footer');
 footer.setAttribute('class', 'topics');
 for(let topic of get(repo).topics) {
 let p = document.createElement('p');
 p.textContent = topic;
 footer.appendChild(p);
 }
 return footer;
}
/**
 * @param {Object} repo - The JSON-parsed object containing a repository's data.
 * @returns {Element} An anchor element whose href is set to the given repo's "real" URL.
 */
function anchorFor(repo) {
 var anchor = document.createElement('a');
 anchor.setAttribute('class', 'container-link');
 anchor.setAttribute('href', repo.html_url);
 anchor.setAttribute('target', '_blank');
 return anchor;
}
function createHoverContent() {
 var hoverContent = document.createElement('div');
 hoverContent.setAttribute('class', 'hover-content');
 
 var boldText = document.createElement('strong');
 boldText.textContent = 'View on GitHub';
 var externalLinkIcon = document.createElement('i');
 externalLinkIcon.setAttribute('class', 'fas fa-external-link-alt');
 
 hoverContent.appendChild(boldText);
 hoverContent.appendChild(externalLinkIcon);
 return hoverContent;
}
function publishRepoCards() {
 const projects = document.getElementById('projects');
 const placeholder = document.getElementById('project-placeholder');
 for (let repo of repos.values()) {
 projects.insertBefore(repo.card, placeholder);
 }
}
/* ============================================
 General top-level styling
 ============================================
*/
* {
 box-sizing: border-box;
}
:root {
 --main-bg-color: white;
 --nav-bg-color: rgb(44, 44, 44);
 --nav-text-color: rgb(179, 177, 177);
 --nav-min-height: 50px;
 --topic-label-bg-color: #e7e7e7;
 --hr-color: rgba(143, 142, 142, 0.2);
 --text-color-normal: black;
 --text-color-emphasis: black;
 --link-color: rgb(39, 83, 133);
 --button-bg-color: rgb(39, 83, 133);
 --button-bg-hover-color: rgb(83, 129, 182);
 --button-text-color: white;
 --button-text-hover-color: white;
 --skill-hover-bg-color: whitesmoke;
 
 --project-card-bg-color: rgb(253, 253, 253);
 --project-card-shadow: 0px 0px 4px 2px rgba(50, 50, 50, 0.4);
 --project-card-shadow-hover: 0px 1px 6px 2px rgba(10, 10, 10, 0.6);
 --project-card-margin: 30px;
 
 --form-bg-color: rgb(255, 255, 255);
 --form-input-margins: 10px;
 --form-max-width: 475px;
 --page-center-percentage: 80%;
 --global-transition-duration: 0.5s;
 --institution-info-border-width: 3px;
}
.night {
 --main-bg-color: rgb(44, 44, 44);
 --nav-bg-color: rgb(10, 10, 10);
 --topic-label-bg-color: #222222;
 
 --hr-color: rgba(255, 255, 255, 0.2);
 --text-color-normal: rgb(179, 177, 177);
 --text-color-emphasis: rgb(202, 202, 202);
 --link-color: rgb(202, 183, 143);
 
 --button-bg-color: rgb(90, 90, 66);
 --button-bg-hover-color: rgb(141, 141, 114);
 --button-text-color: var(--text-color-emphasis);
 --button-text-hover-color: rgb(24, 24, 24);
 
 --skill-hover-bg-color: rgb(66, 66, 66);
 --project-card-bg-color: rgb(54, 54, 54);
 /* The shadows need to be a bit more prominent so they contrast well in dark mode,
 hence the larger values for blur and spread */
 --project-card-shadow: 0 2px 6px 4px rgba(31, 31, 31, 0.9);
 --project-card-shadow-hover: 0px 2px 10px 5px rgba(10, 10, 10, 0.6);
 --form-bg-color: var(--skill-hover-bg-color);
}
#intro {
 margin-bottom: 40px;
}
#about-me, #projects, #skills, #education, #contact { 
 /* So the fixed navbar doesn't cover up any content we scroll to */
 margin-top: calc((var(--nav-min-height) + 20px) * -1);
 padding-top: calc(var(--nav-min-height) + 20px);
}
#about-me, #projects, #skills, #education {
 margin-bottom: 120px;
}
body {
 font-family: Nunito, sans-serif;
 color: var(--text-color-normal);
 background-color: var(--main-bg-color);
 transition: background-color var(--global-transition-duration);
 
 width: var(--page-center-percentage);
 margin-left: auto;
 margin-right: auto;
}
i, h1, h2, h4, strong, em {
 color: var(--text-color-emphasis);
}
.institution-info h4 {
 margin-left: 10px;
 font-weight: normal;
 color: var(--text-color-normal);
}
h1 {
 font-size: 2em;
 margin-block-start: 0.67em;
 margin-block-end: 0.67em;
}
h1, h2 {
 margin-top: 0;
}
a {
 color: var(--link-color);
}
p {
 color: var(--text-color-normal);
}
/* Links an entire parent container, but the parent must be set to relative position */
.container-link {
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 text-decoration: none;
 z-index: 1;
}
/* ============================================
 Buttons, collapsibles, etc.
 ============================================
*/
/* Note: this is an anchor with a class of button */
.button {
 width: 100%;
 height: 40px;
 line-height: 40px;
 text-align: center;
 display: block;
 
 margin-bottom: 10px;
 margin-right: 15px;
 border-radius: 10px;
 
 font-size: 1em;
 font-weight: bold;
 text-decoration: none;
}
.collapsible {
 font-family: Nunito, sans-serif;
 font-size: 1em;
 display: flex;
 align-items: center;
 border: none;
 outline: none;
 width: 100%;
}
.collapsible span {
 text-align: left;
 padding-left: 10px;
 margin-top: 20px;
 margin-bottom: 20px;
}
.button, .collapsible {
 cursor: pointer;
 border: none;
 background-color: var(--button-bg-color);
 transition: var(--global-transition-duration);
}
.button, .button *, .collapsible * {
 color: var(--button-text-color);
}
.button:hover, .collapsible:hover {
 background-color: var(--button-bg-hover-color);
}
/* To get rid of Firefox's dotted lines when these are clicked */
.button::-moz-focus-inner, .collapsible::-moz-focus-inner {
 border: 0;
}
.button:hover, .button:hover *, .collapsible:hover * {
 color: var(--button-text-hover-color);
}
button:focus {
 outline: none;
}
.fa-angle-right, .fa-angle-down {
 margin-left: 10px;
 margin-right: 20px;
 font-size: 1em;
}
@media only screen and (min-width: 400px) {
 
 .main-buttons {
 display: flex;
 }
 .button {
 max-width: 200px; 
 }
}
/* ============================================
 Navigation (+ night mode nightmode-switch)
 ============================================
*/
#topnav .centered-content {
 width: var(--page-center-percentage);
 margin-left: auto;
 margin-right: auto;
 height: var(--nav-min-height);
 display: flex;
 justify-content: space-between;
 align-items: center;
}
#topnav {
 width: 100%;
 min-height: var(--nav-min-height);
 position: fixed;
 left: 0;
 right: 100%;
 top: 0;
 background-color: var(--nav-bg-color);
 /* This is to ensure that it always appears above everything. */
 z-index: 100;
}
#topnav * {
 color: var(--nav-text-color);
}
.nav-links {
 padding: 0;
 list-style-type: none;
 display: none;
 margin-left: 0;
 margin-right: 0;
}
.nav-links li {
 text-align: center;
 margin: 20px auto;
}
.nav-links a {
 text-decoration: none;
 vertical-align: middle;
 transition: var(--global-transition-duration);
}
#topnav .nav-links a:hover {
 text-decoration: underline;
 color: white;
}
.navbar-hamburger {
 font-size: 1.5em;
}
.nightmode-switch-container, .nightmode-switch-container * {
 display: inline-block;
}
.nightmode-switch {
 width: 40px;
 height: 20px;
 line-height: 15px;
 margin-right: 5px;
 background-color: var(--nav-bg-color);
 border: 3px solid var(--nav-text-color);
 border-radius: 100px;
 cursor: pointer;
 transition: var(--global-transition-duration);
}
.nightmode-switch::before {
 content: "";
 display: inline-block;
 vertical-align: middle;
 line-height: normal;
 margin-left: 2px;
 margin-bottom: 2px;
 width: 12px;
 height: 10px;
 
 background-color: var(--nav-text-color);
 border-radius: 50%;
 transition: var(--global-transition-duration);
}
.night .nightmode-switch::before {
 margin-left: 20px;
}
.nav-links.active {
 display: block;
 background-color: var(--nav-bg-color);
 color: var(--nav-text-color);
 
 /* Make the dropdown take up 100% of the viewport width */
 position: absolute;
 left: 0;
 right: 0;
 top: 20px;
}
@media only screen and (min-width: 820px) {
 /* This is the most important part: shows the links next to each other
 Note: .nav-links.active accounts for an edge case where you open the hamburger
 on a small view and then resize the browser so it's larger.
 */
 .nav-links, .nav-links.active {
 margin: 0;
 position: static;
 display: flex;
 flex-direction: row;
 justify-content: flex-end;
 align-items: center;
 font-size: 1.1em;
 }
 .nav-links li {
 margin: 0;
 }
 .nav-links a {
 margin-left: 40px;
 transition: var(--global-transition-duration);
 }
 .navbar-hamburger {
 display: none;
 }
}
/* ============================================
 Page header (intro, about me)
 ============================================
*/
#page-header {
 margin-top: 100px;
 display: grid;
 column-gap: 50px;
 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
#main-cta {
 margin-bottom: 30px;
 font-size: 1.1em;
}
/* ============================================
 Projects/portfolio cards 
 ============================================
*/
#projects {
 display: grid;
 column-gap: 50px;
 row-gap: 50px;
 /* Fill up space as it's made available, with each card being a minimum of 250px */
 grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
/* Don't treat the project header as an item/card, keep it on the top row */
#projects h2 {
 grid-row: 1;
 grid-column: 1 / -1;
}
.project {
 /* To ensure that .project-link (see below) is absolute relative to us and not the page */
 position: relative;
 display: grid;
 grid-template-columns: 1;
 /* Header, description, footer, respectively */
 grid-template-rows: max-content 1fr max-content;
 row-gap: 20px;
}
/* All project cards except the placeholder get a background and box shadow */
.project:not(#project-placeholder) {
 background-color: var(--project-card-bg-color);
 box-shadow: var(--project-card-shadow);
 border-radius: 5px;
 transition: all var(--global-transition-duration);
}
/* Apply margins to all project headers except the placeholder's */
.project:not(#project-placeholder) header {
 margin-top: var(--project-card-margin);
 margin-bottom: 0px;
 margin-left: var(--project-card-margin);
 margin-right: var(--project-card-margin);
 display: grid;
 grid-template-areas: "heading heading rating";
}
.project-icon * {
 width: 24px;
 margin-right: 3px;
 display: inline-block;
 vertical-align: middle;
}
.project h4 {
 margin: 0px;
 align-self: center;
 grid-area: heading;
}
.project-rating {
 font-size: 0.85em;
 justify-self: center;
 align-self: center;
 grid-area: rating;
}
.project .description {
 margin-top: 0px;
 margin-bottom: 0px;
 margin-left: var(--project-card-margin);
 margin-right: var(--project-card-margin);
}
/* Displayed when a user hovers over a project card */
.hover-content {
 font-size: 1.2em;
 /* Again, note that .project has position: relative */
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 /* Center the content for the hover layer */
 display: flex;
 flex-direction: column;
 align-items: center;
 justify-content: center;
 /* Opacity = 0 means it's hidden by default */
 opacity: 0;
 background-color: var(--skill-hover-bg-color);
 transition: var(--global-transition-duration) ease;
}
/* Make it clearer which card is hovered over */
.project:hover:not(#project-placeholder) {
 box-shadow: var(--project-card-shadow-hover);
}
/* Transition for the hover content changes its opacity */
.project:hover .hover-content {
 cursor: pointer;
 opacity: 0.92;
}
.fa-external-link-alt {
 margin-top: 20px;
}
.project-name {
 color: var(--link-color);
 text-decoration: none;
}
.topics {
 display: flex;
 flex-wrap: wrap;
 grid-row: 3;
 margin-top: 0px;
 margin-bottom: var(--project-card-margin);
 margin-left: var(--project-card-margin);
 margin-right: var(--project-card-margin);
}
.topics p {
 font-size: 0.9em;
 padding: 5px;
 margin-top: 10px;
 margin-bottom: 0px;
 margin-right: 10px;
 border-radius: 2px;
 background-color: var(--topic-label-bg-color);
 box-shadow: 0 0 2px black;
 transition: background-color var(--global-transition-duration);
}
#project-placeholder {
 display: flex;
 flex-direction: column;
 text-align: center;
 justify-content: center;
}
.github-cta {
 display: inline-block;
 
 font-size: 3em;
 margin-top: 20px;
 text-decoration: none;
 color: black;
}
/* ============================================
 Skills (responsive columns)
 ============================================
*/
#skills {
 display: grid;
 column-gap: 50px;
 row-gap: 20px;
 grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}
#skills h2 {
 grid-row: 1;
 grid-column: 1 / -1;
}
.skill-category h4 {
 margin-bottom: 5px;
}
.skill-item {
 margin-top: 10px;
 display: grid;
 column-gap: 10px;
 grid-template-columns: 1fr 1fr;
}
.skill-item:hover {
 background-color: var(--skill-hover-bg-color);
}
.skill-name {
 grid-column: 1;
}
.skill-rating {
 grid-column: 2;
 display: inline;
 text-align: right;
}
.fa-star.filled {
 color: var(--button-bg-color);
}
.fa-star.empty {
 color: var(--nav-text-color);
}
.night .fa-star.filled {
 color: rgb(145, 145, 145);
}
.night .fa-star.empty {
 color: var(--button-bg-color);
}
/* ============================================
 Education (institutions, coursework, etc.)
 ============================================
*/
.institution {
 margin-top: 20px;
}
/* Course and award container */
.institution-info {
 display: grid;
 /* Mobile first: only one column. Changes to two columns on bigger screens. See media query below. */
 grid-template-columns: 1fr;
 /* Will be set to a sufficiently large max-height by corresponding click handler for .collapsible */
 max-height: 0px;
 transition: max-height var(--global-transition-duration);
 overflow: hidden;
 border: solid var(--institution-info-border-width) var(--button-bg-color);
 border-top: none;
}
.institution-info .awards {
 /* Only matters on mobile, where the awards are stacked underneath courses */
 border-top: solid var(--institution-info-border-width) var(--button-bg-color);
}
.institution-info ul {
 padding-right: 10px;
}
.institution-info p {
 padding-left: 10px;
}
/* Line up courses and awards side by side on larger screens */
@media only screen and (min-width: 800px) {
 .institution-info {
 grid-template-rows: 1fr;
 grid-template-columns: auto auto;
 }
 .institution-info .awards {
 /* Now that it's lined up to the right of the courses, there's no need for a top border */
 border-top: none;
 /* But there is for a left border */
 border-left: solid var(--institution-info-border-width) var(--button-bg-color);
 }
}
/* ============================================
 Contact form
 ============================================
*/
#contact {
 display: grid;
 grid-template-areas: "form"
 "socials";
 grid-template-rows: auto;
 column-gap: 50px;
}
#contact-form {
 grid-area: form;
}
#social-networks {
 grid-area: socials;
}
@media only screen and (min-width: 700px) {
 #contact {
 grid-template-areas: "form form form socials";
 }
}
form {
 margin-bottom: 50px;
 margin-top: 30px;
 max-width: var(--form-max-width);
}
form * {
 color: var(--text-color-normal);
 font-family: Nunito, sans-serif;
 font-size: 1em;
}
form input:not([class="button"]), form textarea {
 height: 30px;
 width: 100%;
 margin-bottom: 15px;
 padding: 10px;
 
 background-color: var(--form-bg-color);
 border: 0px solid;
 box-shadow: 0 0 3px 1px rgb(172, 172, 172);
 border-radius: 3px;
 transition: var(--global-transition-duration);
}
form label {
 margin-bottom: 5px;
 display: block;
}
form input:focus, form textarea:focus {
 outline: none;
 box-shadow: 0 0 5px 2px rgb(155, 155, 155);
}
form textarea {
 max-width: var(--form-max-width);
 min-height: 200px;
 transition: height 0s;
 transition: background-color var(--global-transition-duration);
}
form .button {
 max-width: 100%;
 width: 100%;
 height: 45px;
}
/* Yum, honey */
input.honeypot {
 display: none;
}
/* ============================================
 Social networks
 ============================================
*/
#social-networks {
 display: grid;
 grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
 grid-template-rows: min-content;
 grid-auto-rows: min-content;
 row-gap: 50px;
 column-gap: 30px;
 margin-bottom: 50px;
}
#social-networks h3 {
 grid-row: 1;
 grid-column: 1 / -1;
}
.social-network {
 /* Position relative because we have an absolutely 
 positioned .container-link as a child */
 position: relative;
 display: grid;
 grid-template-columns: auto 1fr;
 column-gap: 20px;
}
.social-network:hover {
 cursor: pointer;
 background-color: var(--skill-hover-bg-color);
}
.social-network .fa-stack {
 grid-column: 1;
 display: grid;
}
.fa-stack i {
 align-self: center;
 justify-self: center;
}
/* Whatever icon is being used as the background one */
.fa-stack-2x {
 opacity: 0;
 font-size: 1.5em;
 color: white;
}
.night .fa-stack-2x {
 opacity: 1;
}
.social-network .network-name {
 grid-column: 2;
 align-self: center;
}
#social-networks .fa-linkedin {
 color: #0077B5;
}
#social-networks .fa-github {
 color: black;
}
#social-networks .fa-stack-exchange {
 color: #195398;
}
#social-networks .fa-address-book {
 color: #37A000;
}
#page-footer {
 position: absolute;
 left: 0;
 height: 50px;
 width: 100%;
 background: var(--nav-bg-color);
 color: var(--nav-text-color);
 display: flex;
 justify-content: center;
 align-items: center;
}
<!DOCTYPE html>
<html lang="en">
 <head>
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <!-- Nunito font looks amazing :) -->
 <link href="https://fonts.googleapis.com/css?family=Nunito&display=swap" rel="stylesheet">
 <!-- Font Awesome icons -->
 <script src="https://kit.fontawesome.com/7d7dc6ad85.js"></script>
 <!-- Custom stylesheet -->
 <link rel="stylesheet" href="style.css">
 <!-- Favicon -->
 <link rel="icon" href="favicon.ico" type='image/x-icon'>
 <!-- Preview image (e.g., for LinkedIn or Facebook) -->
 <meta property="og:image" content="https://avatars2.githubusercontent.com/u/19352442?s=400&amp;v=4">
 <title>Aleksandr Hovhannisyan</title>
 <!-- Contact form -->
 <meta name="referrer" content="origin">
 </head>
 <body>
 <nav id="topnav">
 <div class="centered-content">
 <div class="nightmode-switch-container">
 <div class="nightmode-switch"></div><span>Light mode</span>
 </div>
 <i class="navbar-hamburger fas fa-bars"></i>
 <ul class="nav-links">
 <li><a href="#about-me">About Me</a></li>
 <li><a href="#projects">Projects</a></li>
 <li><a href="#skills">Skills</a></li>
 <li><a href="#education">Education</a></li>
 <li><a href="#contact">Contact</a></li>
 </ul>
 </div>
 </nav>
 <article id="content">
 
 <section id="projects">
 <h2>Projects &#128193;</h2>
 <aside id="project-placeholder" class="project">
 <header>
 <h4>Want to see more of my work?</h4>
 </header>
 <div>
 <p>Check out my other repos:</p>
 <a class="github-cta" href="https://github.com/AleksandrHovhannisyan?tab=repositories" target="_blank"><i class="fab fa-github"></i></a>
 </div>
 </aside>
 </section>
 </article>
 <!-- Custom javascript -->
 <script src="index.js"></script>
 </body>
</html>
edited title
Link
BCdotWEB
  • 11.4k
  • 2
  • 28
  • 45

AJAX and JavaScript: Pulling data from the GitHub API for user repositories

Get rid of jquery CDN
Source Link
Loading
Source Link
Loading
lang-js

AltStyle によって変換されたページ (->オリジナル) /