~kvikshaug/kvikshaug.no

167b5d076c2ef9cc74115dc70434a95d0a2aacf4 — Ali Kaafarani 1 year, 11 months ago 2cda737
Content overhaul
22 files changed, 205 insertions(+), 731 deletions(-)

M .gitignore
D assets/CV - Ali Kaafarani.pdf
D assets/images/bitcoin-qr.png
D assets/images/caffeine.webp
D assets/images/memote.webp
D assets/images/splitboard.webp
D assets/images/test.png
D assets/images/unseen-bio.webp
D assets/images/ut.no.webp
M assets/sass/_content.scss
M assets/sass/_content_responsive.scss
M assets/sass/_design.scss
D assets/sass/_layout.scss
D assets/sass/_layout_responsive.scss
M assets/sass/style.scss
D data/.gitkeep
D script/analytics.js
M script/console.js
D script/navigation.js
M src/kvikshaug/app.py
M templates/layout.html
M tests/integration/test_endpoints.py
M .gitignore => .gitignore +0 -1
@@ 5,5 5,4 @@
/.env
/assets/css/
/assets/script/
/data/guestbook.json
/logs/access.log

D assets/CV - Ali Kaafarani.pdf => assets/CV - Ali Kaafarani.pdf +0 -0
D assets/images/bitcoin-qr.png => assets/images/bitcoin-qr.png +0 -0
D assets/images/caffeine.webp => assets/images/caffeine.webp +0 -0
D assets/images/memote.webp => assets/images/memote.webp +0 -0
D assets/images/splitboard.webp => assets/images/splitboard.webp +0 -0
D assets/images/test.png => assets/images/test.png +0 -0
D assets/images/unseen-bio.webp => assets/images/unseen-bio.webp +0 -0
D assets/images/ut.no.webp => assets/images/ut.no.webp +0 -0
M assets/sass/_content.scss => assets/sass/_content.scss +69 -113
@@ 1,147 1,103 @@
article {
header {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 8rem;
  padding: 8rem 4rem;
  min-height: 100vh;
  padding: 80px 80px;
  font-size: 2rem;

  &.introduction {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 120px;

    .left {
      justify-self: right;
      text-align: right;
  .image {
    text-align: center;

      h1 {
        font-size: 120px;
        font-weight: normal;
      }
      .text {
        display: flex;
        flex-direction: column;
        align-items: flex-end;
        gap: 10px;
        font-size: 40px;

        p {
          max-width: 800px;
        }
      }
    }
    img {
      justify-self: left;
      min-width: 20rem;
    }
  }

  &.interests {
    display: grid;
    grid-template-columns: 0.7fr 1.3fr;
    column-gap: 120px;
  .links {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-evenly;
    gap: 2rem;
    flex-grow: 1;
    max-width: 40rem;

    .left {
      justify-self: right;
    .link {
      display: flex;
      flex-direction: column;
      row-gap: 40px;

      h2 {
        text-align: center;
      }
      align-items: center;
      gap: 1rem;
      text-align: center;
      font-size: 2rem;
      flex-basis: 180px;

      .links {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        justify-items: center;
        row-gap: 40px;
        column-gap: 40px;
        max-width: 400px;

        .link {
          text-align: center;
          font-weight: bold;

          .sourcehut {
            margin: 0 auto;
            width: 48px;
            height: 48px;
            fill: #bf3211;
          }
        }
      i {
        font-size: 6rem;
      }
    }

    .right {
      justify-self: left;

      p {
        max-width: 800px;
      .sourcehut {
        width: 6rem;
        height: 6rem;
        fill: #bf3211;
      }
    }
  }
}

  &.projects {
    display: flex;
    flex-direction: column;
    row-gap: 80px;
article.quote {
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: 12rem;
  align-items: center;
  min-height: 100vh;
  padding: 8rem;

    .grid {
      display: grid;
      grid-template-columns: 1fr 350px;
      align-items: center;
      column-gap: 80px;
  .cell {
    max-width: 50rem;

      .right {
        img {
          max-width: 350px;
          box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.4);
        }
      }
    &.left {
      justify-self: right;
    }

      .subheader {
        font-style: italic;
      }
    &.right {
      justify-self: left;
    }
  }

  &.profile {
    .actions {
      display: grid;
      grid-template-columns: 1fr 1fr;
      justify-items: center;
      column-gap: 80px;
      margin-bottom: 80px;

      .action {
        display: flex;
        flex-direction: column;
        align-items: center;
        text-align: center;
    blockquote {
      position: relative;
      font-size: 3rem;

      &::before {
        position: absolute;
        z-index: 1;
        top: -50px;
        left: -80px;
        font-size: 200px;
        line-height: 1;
        color: #cccccc;
        opacity: 0.6;
        content: "“";
      }
    }

    h2 {
      margin-top: 80px;
    .author {
      margin-top: 4rem;
      text-align: right;
    }
  }
}

    .keywords {
      display: grid;
      grid-template-columns: 1fr 1fr;
      column-gap: 30px;
      row-gap: 30px;

      margin-top: 30px;
footer {
  padding: 8rem 0;

      dl {
        dt {
          font-weight: bold;
        }
  .content {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 40px;

        dd {
          margin-left: 30px;
        }
      }
    code {
      overflow-wrap: anywhere;
    }
  }
}

M assets/sass/_content_responsive.scss => assets/sass/_content_responsive.scss +41 -78
@@ 1,107 1,70 @@
@media (max-width: 1800px) {
  article {
    padding-left: 80px;
@media (max-width: 991px) {
  header {
    display: flex;
    flex-direction: column;
    min-height: auto;
    padding: 3rem 1.5rem;

    &:first-of-type {
      padding-top: 40px;
    }
    .links {
      gap: 3rem;
      max-width: auto;

    &.introduction {
      column-gap: 40px;
      .link {
        flex-basis: 135px;
        font-size: 1.5rem;

      .left {
        h1 {
          font-size: 90px;
        i {
          font-size: 4rem;
        }

        .text {
          font-size: 30px;

          p {
            max-width: none;
          }
        .sourcehut {
          width: 4rem;
          height: 4rem;
        }
      }
      img {
        justify-self: center;
      }
    }
  }
}

@media (max-width: 991px) {
  article {
  article.quote {
    grid-template-columns: 1fr;
    gap: 8rem;
    min-height: auto;
    padding-left: 20px;
    padding-right: 20px;
    padding: 3rem 1.5rem;

    &.introduction {
      grid-template-columns: 1fr;
      row-gap: 80px;
    .cell {
      max-width: none;
      justify-self: stretch;

      .left {
        text-align: left;
      &.left {
        margin: 0 2rem;
      }
    }

    &.interests {
      grid-template-columns: 1fr;
      row-gap: 60px;
      blockquote {
        font-size: 2rem;

      .left {
        justify-self: center;
      }

      .right {
        p {
          max-width: none;
        &::before {
          top: -40px;
          left: -45px;
          font-size: 120px;
        }
      }
    }

    &.projects {
      .grid {
        grid-template-columns: 1fr;
        margin-top: 40px;

        .right {
          grid-row: 1;
        }
      .author {
        margin-top: 2rem;
      }
    }
  }

    &.profile {
      .actions {
        column-gap: 40px;
      }
    }
  footer {
    padding: 3rem 0;
  }
}

@media (max-width: 575px) {
  article {
    &.introduction {
      .left {
        h1 {
          font-size: 70px;
        }
      }
    }

    &.interests {
      .left {
        .links {
          grid-template-columns: repeat(2, 1fr);
        }
      }
    }

    &.profile {
      .keywords {
        grid-template-columns: 1fr;
        row-gap: 40px;
        margin-top: 40px;
      }
  footer {
    .content {
      grid-template-columns: 1fr;
      gap: 1.5rem;
    }
  }
}

M assets/sass/_design.scss => assets/sass/_design.scss +41 -37
@@ 36,15 36,13 @@ html {

body {
  font-family: "EB Garamond", Garamond, Georgia, serif;
  font-size: 24px;
  font-size: 1.5rem;
}

p {
  margin-top: 40px;
}

ul {
  margin-top: 30px;
.paragraphs {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

a {


@@ 59,6 57,14 @@ img {
  height: auto;
}

hr {
  height: 0;
  margin-top: 2rem;
  margin-bottom: 2rem;
  border-bottom: 0;
  border-top: 1px solid black;
}

//
// Structure
//


@@ 75,40 81,38 @@ img {
// Themes
//

.theme {
  &.white-on-black {
    background-color: black;
    color: white;
    a,
    a:visited {
      color: #cfba58;
    }
.theme-light {
  background-color: #fffff8;
  color: #1b1b1b;
  a,
  a:visited {
    color: #bf3211;
  }

  &.black-on-white {
    background-color: white;
    color: #1b1b1b;
    a,
    a:visited {
      color: #bf3211;
    }
  hr {
    border-top-color: #bf3211;
  }
}

  &.brown-light {
    background-color: #f0efd1;
    color: #1b1b1b;
    a,
    a:visited {
      color: #bf3211;
    }
.theme-light-accent {
  background-color: #f0efd1;
  color: #1b1b1b;
  a,
  a:visited {
    color: #bf3211;
  }
  hr {
    border-top-color: #bf3211;
  }
}

  &.brown-dark {
    background-color: #21180d;
    color: #f9f8cc;
    a,
    a:visited {
      color: #cfba58;
    }
.theme-dark {
  background-color: #21180d;
  color: #f9f8cc;
  a,
  a:visited {
    color: #cfba58;
  }
  hr {
    border-top-color: #cfba58;
  }
}

D assets/sass/_layout.scss => assets/sass/_layout.scss +0 -121
@@ 1,121 0,0 @@
@use "design";

nav.large-screens {
  position: fixed;
  z-index: 100;
  top: 40px;
  right: 40px;
  mix-blend-mode: difference;

  ul {
    display: flex;
    column-gap: 40px;
    margin-top: 0;
    padding-left: 0;
    list-style-type: none;

    a {
      padding: 10px 0;
      border-bottom: 2px solid;
      border-bottom-color: rgba(255, 255, 255, 0);
      font-weight: bold;
      transition: border-bottom-color 0.3s;

      &,
      &:visited {
        color: white;
      }

      &.active {
        border-bottom-color: rgba(255, 255, 255, 1);
      }
    }
  }
}

nav.small-screens {
  display: none;
}

footer {
  padding: 80px 0;

  hr {
    height: 0;
    margin: 40px auto;
    border: 0;
    border-top: 1px solid #cfba58;
  }

  .content {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 40px;

    code {
      overflow-wrap: anywhere;
    }

    .qr {
      margin-top: 20px;
    }
  }

  .guestbook {
    margin-top: 80px;

    .messages {
      display: flex;
      flex-direction: column;

      .message {
        display: grid;
        grid-template-columns: 1fr auto;
        column-gap: 40px;
        padding: 60px 0;

        &:hover {
          opacity: 0.8;
        }
      }

      hr {
        margin: 0;
      }
    }

    .form {
      display: flex;
      flex-direction: column;
      row-gap: 20px;
      margin-top: 20px;

      input,
      textarea {
        display: block;
        padding: 8px;
        border: 1px solid #cfba58;
        background: inherit;
        color: inherit;

        &:focus {
          border-width: 2px;
          padding: 7px;
          outline: none;
        }
      }

      button {
        padding: 8px;
        background-color: #f0efd1;
        color: #1b1b1b;
        border: 0;

        &:hover {
          opacity: 0.6;
          cursor: pointer;
        }
      }
    }
  }
}

D assets/sass/_layout_responsive.scss => assets/sass/_layout_responsive.scss +0 -45
@@ 1,45 0,0 @@
@media (max-width: 991px) {
  nav.large-screens {
    display: none;
  }

  nav.small-screens {
    position: fixed;
    z-index: 200;
    left: 0;
    bottom: 0;
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    column-gap: 10px;
    justify-content: center;
    padding: 10px;
    width: 100%;
    background-color: white;
    font-size: 14px;
    text-align: center;

    a {
      display: flex;
      flex-direction: column;
      row-gap: 6px;
      color: #1b1b1b;

      &.active {
        color: #bf3211;
      }
    }
  }

  footer {
    // Add the height of the small-screen navigation bar, approx. 73px
    padding-bottom: 40px + 73px;
  }
}

@media (max-width: 575px) {
  footer {
    .content {
      grid-template-columns: 1fr;
    }
  }
}

M assets/sass/style.scss => assets/sass/style.scss +0 -2
@@ 1,7 1,5 @@
@use "reset";
@use "design";
@use "design_responsive";
@use "layout";
@use "layout_responsive";
@use "content";
@use "content_responsive";

D data/.gitkeep => data/.gitkeep +0 -0
D script/analytics.js => script/analytics.js +0 -10
@@ 1,10 0,0 @@
(function (w, d, s, l, i) {
  w[l] = w[l] || [];
  w[l].push({ "gtm.start": new Date().getTime(), event: "gtm.js" });
  var f = d.getElementsByTagName(s)[0],
    j = d.createElement(s),
    dl = l != "dataLayer" ? "&l=" + l : "";
  j.async = true;
  j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl;
  f.parentNode.insertBefore(j, f);
})(window, document, "script", "dataLayer", "GTM-5MM25KP");

M script/console.js => script/console.js +1 -9
@@ 19,13 19,5 @@
    }
    output += "\n";
  }

  output +=
    "\n%cScience is not only compatible with spirituality; it is a profound source of spirituality.\n\n%c- Carl Sagan";
  console.log(
    output,
    "display:block;text-align:center;color:#1b1b1b",
    "display:block;margin:0 auto;width:300px;text-align:center;color:#bf3211;",
    "display:block;margin:0 auto;width:300px;text-align:right;color:#1b1b1b;"
  );
  console.log(output, "display:block;text-align:center;color:#1b1b1b");
})();

D script/navigation.js => script/navigation.js +0 -21
@@ 1,21 0,0 @@
(function () {
  const elements = document.querySelectorAll("[data-trigger-nav]");
  const handler = () => {
    elements.forEach((element) => {
      const rect = element.getBoundingClientRect();
      const isInViewport =
        rect.top <= document.documentElement.clientHeight / 2 &&
        rect.bottom >= document.documentElement.clientHeight / 2;

      document
        .querySelectorAll(
          `nav a[href='#${element.getAttribute("data-trigger-nav")}']`
        )
        .forEach((element) => {
          element.classList.toggle("active", isInViewport);
        });
    });
  };
  document.addEventListener("scroll", handler);
  window.addEventListener("DOMContentLoaded", handler);
})();

M src/kvikshaug/app.py => src/kvikshaug/app.py +2 -36
@@ 1,8 1,4 @@
from datetime import datetime
import json
import os

from flask import Flask, render_template, request, redirect
from flask import Flask, render_template
from jinja2 import StrictUndefined

from .settings import Configuration


@@ 16,37 12,7 @@ if app.config["DEBUG"]:
    # Be strict during development; forgiving in production.
    app.jinja_env.undefined = StrictUndefined

# Initialize guestbook if not yet created.
if not os.path.exists("data/guestbook.json"):
    with open("data/guestbook.json", "w") as file_:
        json.dump([], file_)


@app.route("/")
def home():
    with open("data/guestbook.json") as file_:
        guestbook = [c for c in json.load(file_) if c["published"]]
    for message in guestbook:
        message["datetime"] = datetime.strptime(message["datetime"], "%Y-%m-%d %H:%M")
    guestbook = sorted(guestbook, key=lambda m: m["datetime"], reverse=True)
    return render_template("layout.html", guestbook=guestbook)


@app.route("/guestbook", methods=["POST"])
def guestbook():
    message = request.form.get("message", "").strip()
    if message == "":
        return redirect("/#guestbook")
    with open("data/guestbook.json") as file_:
        guestbook = json.load(file_)
    with open("data/guestbook.json", "w") as file_:
        guestbook.append(
            {
                "message": message,
                "ip": request.remote_addr,
                "datetime": datetime.now().strftime("%Y-%m-%d %H:%M"),
                "published": False,
            }
        )
        json.dump(guestbook, file_)
    return redirect("/#guestbook")
    return render_template("layout.html")

M templates/layout.html => templates/layout.html +51 -248
@@ 17,266 17,69 @@
  </head>

  <body>
    <nav class="large-screens">
      <ul>
        <li><a href="#introduction">Hi, I'm Ali!</a></li>
        <li><a href="#interests">What I'm into</a></li>
        <li><a href="#projects">What I've worked on</a></li>
        <li><a href="#profile">How I work</a></li>
        <li><a href="#contact">Get in touch</a></li>
      </ul>
    </nav>
    <nav class="small-screens">
      <a href="#introduction">
        <i class="fa-solid fa-hand-peace fa-2x"></i>
        Hi!
      </a>
      <a href="#interests">
        <i class="fa-solid fa-person-snowboarding fa-2x"></i>
        Likes
      </a>
      <a href="#projects">
        <i class="fa-solid fa-medal fa-2x"></i>
        Work
      </a>
      <a href="#profile">
        <i class="fa-brands fa-uncharted fa-2x"></i>
        Profile
      </a>
      <a href="#contact">
        <i class="fa-solid fa-comment-dots fa-2x"></i>
        Chat
      </a>
    </nav>

    <article id="introduction" class="introduction theme white-on-black" data-trigger-nav="introduction">
      <div class="left">
        <h1>Hey there! I'm Ali!</h1>
        <div class="text">
          <p>I'm a software engineer and overall pretty cool nerd. Thank you for dropping by my little piece of the internet—I'm always excited to get visitors!</p>
          <p>I currently work at <a href="https://unseenbio.com">Unseen Bio</a> and live in Copenhagen together with my best friend and life partner Irene.</p>
          <p>Perhaps you'd like to <a href="#interests">get to know me better</a>, check out <a href="#projects">what I'm up to</a>, or <a href="#contact">get in touch</a>.</p>
        </div>
    <header class="theme-light">
      <div class="image">
        <img src="/assets/images/hello.webp" title="Hi there, I'm Ali!" alt="A picture of me, modified with Google DeepDream. The picture is dominated by teal, purple and blue colors and has psychedelic undertones. However, it has a smooth appearance and does not seem to produce any immediate pareidolia effect." class="hello" width="407" height="610">
      </div>
      <img src="/assets/images/hello.webp" alt="A picture of me, modified with Google DeepDream. The picture is dominated by teal, purple and blue colors and has psychedelic undertones. However, it has a smooth appearance and does not seem to produce any immediate pareidolia effect." class="hello" width="407" height="610">
    </article>

    <article id="interests" class="interests theme black-on-white" data-trigger-nav="interests">
      <div class="left">
        <h2>Stuff I'm into</h2>
        <div class="links">
          <a class="link" title="Vimeo" href="https://vimeo.com/kvikshaug">
            <i class="fab fa-2x fa-vimeo"></i><br>Vimeo
          </a>
          <a class="link" title="Oku" href="https://oku.club/user/kvikshaug">
            <i class="fas fa-2x fa-book"></i><br>Oku
          </a>
          <a class="link" title="Discord" href="https://discordapp.com/users/180274566378029056">
            <i class="fab fa-2x fa-discord"></i><br>Discord
          </a>
          <a class="link" title="Steam" href="https://steamcommunity.com/id/kvikshaug">
            <i class="fab fa-2x fa-steam"></i><br>Steam
          </a>
          <a class="link" title="sourcehut" href="https://git.sr.ht/~kvikshaug">
            <svg class="sourcehut" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200z"></path></svg>
            sourcehut
          </a>
          <a class="link" title="GitLab" href="https://gitlab.com/kvikshaug">
            <i class="fab fa-2x fa-gitlab"></i><br>GitLab
          </a>
          <a class="link" title="GitHub" href="https://github.com/kvikshaug">
            <i class="fab fa-2x fa-github"></i><br>GitHub
          </a>
          <a class="link" title="StackOverflow" href="https://stackoverflow.com/story/kvikshaug">
            <i class="fab fa-2x fa-stack-overflow"></i><br>StackOverflow
          </a>
        </div>
      </div>
      <div class="right">
        <p>Some of my life-long passions include <a href="https://vimeo.com/53413403">snowboarding</a>, <a href="/assets/images/splitboard.webp">splitboarding</a>, kite surfing, cross-country skiing and <a href="https://ut.no/">mountain hiking</a>. It's not all outdoors and adrenaline though; I try to make time to read books, and have accumulated a <a href="https://oku.club/user/kvikshaug/collection/favorites-L1pah">short list of favorites </a>. Additionally, I've loved playing <a href="https://steamcommunity.com/id/kvikshaug">video games</a> since I was 5 years old, and at this point, I don't think I will ever outgrow them.</p>
        <p>I'm generally active in open source and have been <a href="https://github.com/kvikshaug">part of GitHub's ubiquitous git hosting service</a> since 2009. However, after Microsoft's acquisition in 2018, my feelings about the platform are lukewarm. GitLab is a serious contender in the DevOps platform space and currently <a href="https://gitlab.com/kvikshaug">my default choice for new projects</a>. Sourcehut is also an appealing, minimalistic software development platform, and an interesting alternative to the bigger players. I'm also somewhat active on <a href="https://stackoverflow.com/story/kvikshaug">Stack Overflow</a>, the definitive Q&amp;A site for developers. Admittedly, I have more Qs than As, but hey, at some point we have enough Jon Skeets.</p>
        <p>Did you know that there exists galaxies we can see, but never interact with because they're <abbr title="Due to the expansion of spacetime geometry.">receding from us faster than light</abbr>? No signal we send can ever reach them. If some of them harbor life, it is physically impossible for us to <em>ever</em> interact with those lifeforms. They're on their own, and so are we. Yeah, stop and consider that for a moment. Now let's solve <abbr title="Faster than light.">FTL</abbr> travel!</p>
      <div class="links">
        <a class="link" title="Steam" href="https://steamcommunity.com/id/kvikshaug">
          <i class="fab fa-2x fa-steam"></i> Steam
        </a>
        <a class="link" title="Discord" href="https://discordapp.com/users/180274566378029056">
          <i class="fab fa-2x fa-discord"></i> Discord
        </a>
        <a class="link" title="Vimeo" href="https://vimeo.com/kvikshaug">
          <i class="fab fa-2x fa-vimeo"></i> Vimeo
        </a>
        <a class="link" title="Oku" href="https://oku.club/user/kvikshaug">
          <i class="fas fa-2x fa-book"></i> Oku
        </a>
        <a class="link" title="Ravelry" href="https://www.ravelry.com/people/kvikshaug">
          <i class="fab fa-2x fa-ravelry"></i> Ravelry
        </a>
        <a class="link" title="StackOverflow" href="https://stackoverflow.com/users/302484/kvikshaug">
          <i class="fab fa-2x fa-stack-overflow"></i> StackOverflow
        </a>
        <a class="link" title="sourcehut" href="https://git.sr.ht/~kvikshaug">
          <svg class="sourcehut" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200z"></path></svg>
          sourcehut
        </a>
        <a class="link" title="GitLab" href="https://gitlab.com/kvikshaug">
          <i class="fab fa-2x fa-gitlab"></i> GitLab
        </a>
        <a class="link" title="GitHub" href="https://github.com/kvikshaug">
          <i class="fab fa-2x fa-github"></i> GitHub
        </a>
      </div>
    </article>
    </header>

    <article id="projects" class="projects theme brown-light" data-trigger-nav="projects">
      <section id="unseen-bio" class="container">
        <h2>Unseen Bio</h2>
        <div class="grid">
          <div class="left">
            <p class="subheader">
              February 2020 to present<br>
              Copenhagen, Denmark<br>
              Role: Chief Information Officer
            </p>
            <p>Unseen Bio is a fast-growing biotechnology startup which specializes in analyzing the gut microbiome, providing unique personalized insight into the health and composition of your gut.</p>
            <p>We are 5 co-founders with a broad set of competences who came together to execute on a novel approach to gut microbiome analysis. By applying the technology from the <a href="#dd-decaf">DD-DeCaF project</a> to the promising and rapidly advancing field of the gut microbiome, we found that we were in a position to generate a vastly improved and much more personalized picture of the microbiome, than what has been possible before.</p>
            <p>To this aim, we built a unique interactive visualization of the microbiome and how it compares to a healthy reference gut. We then apply metabolic software to build a complete network model of each individual microbiome and the interactions between the different bacteria in that community. With this model, we can make useful predictions of how dietary changes will affect the microbiome positively or negatively.</p>
            <p>The service is fully translated to Danish, German and English, and is available in Europe. Read more on <a href="https://unseenbio.com">unseenbio.com</a>.</p>
          </div>
          <a class="right" href="https://unseenbio.com"><img src="/assets/images/unseen-bio.webp" alt="The Digital Gut, a visualization of a gut microbiome"></a>
        </div>
      </section>

      <section class="container">
        <h2>Memote</h2>
        <div class="grid">
          <div class="left">
            <p class="subheader">
              Novo Nordisk Foundation Center for Biosustainability<br>
              May 2018 to February 2020<br>
              DTU, Lyngby Campus, Denmark<br>
              Role: Software Engineer
            </p>
            <p><a href="https://memote.readthedocs.io/en/latest/">Memote</a> (the <strong>me</strong>tabolic <strong>mo</strong>del <strong>te</strong>st suite) is a tool to analyze and score the quality of genome-scale metabolic models. It assists the process of model reconstruction by helping to quickly identify potential issues or best practice deviations from the community standard.</p>
            <p>In close collaboration with the authors of Memote, I built an <a href="https://memote.io">online service</a> to help promote the tool and make it accessible to anyone with a browser. The service uses the <a href="https://github.com/opencobra/memote">official package</a> and allows visitors to upload any metabolic model to be analyzed in our preconfigured online environment, and eventually receive a full report without having to install the application locally.</p>
            <p>In the same period, I also made a <a href="https://github.com/opencobra/cobrapy/commits?author=kvikshaug">handful of contributions</a> to <a href="https://github.com/opencobra/cobrapy">COBRApy</a>, memote's underlying tool for constraint-based modeling.</p>
            <p>Memote was <a href="https://www.nature.com/articles/s41587-020-0446-y">published in Nature Biotechnology</a> in 2020. A presentation of my modest career in academia is available on <a href="https://orcid.org/0000-0002-2805-310X">ORCID <i class="fab fa-orcid"></i></a>.</p>
          </div>
          <a class="right" href="https://memote.io"><img src="/assets/images/memote.webp" alt="An example memote report for the E. Coli core model"></a>
        </div>
      </section>

      <section id="dd-decaf" class="container">
        <h2>Data-Driven Design of Cell Factories and Communities</h2>
        <div class="grid">
          <div class="left">
            <p class="subheader">
              Novo Nordisk Foundation Center for Biosustainability<br>
              December 2017 to February 2020<br>
              DTU, Lyngby Campus, Denmark<br>
              Role: Software Engineer
            </p>
            <p><a href="https://www.biosustain.dtu.dk/">DTU Biosustain</a> is an interdisciplinary research centre which develops new knowledge and technology to support the production of bio-chemicals using microbial production hosts called cell factories.</p>
            <p>I was a senior developer and technical operations lead on <a href="https://caffeine.dd-decaf.eu">Caffeine</a>, a unique web platform which brings computer-aided design into the realm of metabolic engineering.</p>
            <p><a href="https://dd-decaf.eu">The project</a> was funded through the <a href="https://ec.europa.eu/programmes/horizon2020/en">EU Horizon 2020 programme</a>, and I joined halfway into its 4 year duration. In the final phase of the project, we were invited to participate in the <a href="https://bii.dk/news/meet-the-start-ups-metabolizer/">Business Acceleration Academy program</a> of the BioInnovation Institute in Copenhagen, with the aim to develop a business plan and validate the future commercial potential of the product beyond its EU funding phase. This resulted in <a href="https://youtu.be/v0OykuR2JbA?t=4127">Metabolyzer</a>, which has since joined forces with <a href="https://unseenbio.com">Unseen Bio</a>.</p>
            <p>As part of a 5 person team (a product owner, two bioinformaticians and two software engineers), I quickly received responsibility for large aspects of the project, including major architectural and technical decisions, collaboration and coordination with third party library communities, and fostering a culture of collecting data and metrics in order to make fact-based decisions.</p>
          </div>
          <a class="right" href="https://caffeine.dd-decaf.eu"><img src="/assets/images/caffeine.webp" alt="The interactive map in Caffeine, showing parts of a pathway map with flux results for a metabolic model"></a>
        </div>
      </section>

      <section class="container">
        <h2>Den Norske Turistforening (DNT)</h2>
        <div class="grid">
          <div class="left">
            <p class="subheader">
              Den Norske Turistforening (The Norwegian Trekking Association)<br>
              October 2011 to June 2017<br>
              Oslo, Norway<br>
              Role: Systems Developer
            </p>
            <p><a href="https://www.dnt.no">Den Norske Turistforening (DNT)</a> is Norway's largest outdoor activity organisation with more than 300 000 members, which promotes trekking and mountain hiking, while preserving the Norwegian environment, nature and cultural values.</p>
            <p>I was technical lead for the core web platform, Sherpa, which serves several functions:</p>
            <ul>
              <li>The central web site (www.dnt.no), serving well over a million unique users yearly</li>
              <li>Web sites for all 57 member associations, as well as cabin and campaign sites, amounting in more than 150 published sites</li>
              <li>Integrated content management and administration tools</li>
              <li>Membership services and integration with membership systems</li>
              <li>Data management for DNTs core data (cabins, trails and guided tours)</li>
            </ul>
            <p>Being hired explicitly to start the Sherpa project, I was involved in every part from the initial planning and development, to maintenance, operations and user support, in addition to general maintenance on older systems which the new platform eventually would replace. My responsibilities, varying somewhat over the course of the project, include:</p>
            <ul>
              <li>Initial technology stack and architectural decisions</li>
              <li>Development, configuration and deployment</li>
              <li>Ensuring monitoring, error reporting, logging and system insights</li>
              <li>Analyzing, prioritizing and handling automated error reports and user reported issues</li>
              <li>Planning migration and data transition from older systems</li>
              <li>User testing and quality assurance</li>
              <li>Writing technical documentation, user guides and news bulletins</li>
              <li>Technical support for users within the organization</li>
            </ul>
          </div>
          <a class="right" href="https://ut.no"><img src="/assets/images/ut.no.webp" alt="A digital map"></a>
        </div>
      </section>
    </article>

    <article id="profile" class="profile theme white-on-black" data-trigger-nav="profile">
      <div class="container">
        <div class="actions">
          <a class="action" title="LinkedIn" href="https://www.linkedin.com/in/ali-kaafarani-8723b319/">
            <i class="fab fa-3x fa-linkedin"></i>
            <span>Connect with me on LinkedIn</span>
          </a>
          <a class="action" href="/assets/CV%20-%20Ali%20Kaafarani.pdf">
            <i class="fas fa-3x fa-file-pdf"></i>
            <span>Read my CV</span>
          </a>
        </div>

        <section>
          <p>I am an experienced software developer and IT operations engineer with a pragmatic approach and preference for stability over experimentation. My motivation comes mainly from building something of value and use to others. Although I can still appreciate the intrinsic beauty in a well designed algorithm, elegant data structure or clever expression of computational logic, I see software systems as a means to an end; any perfectly engineered application does not matter if nobody uses it.</p>
          <p>Having often worked in small teams with a large responsibility surface, I have out of necessity learned how to design low-maintenance systems. Instead of specializing in a narrow area, I learned how to be effective and productive across the stack. As a result, my standards for the development experience are very high: Short feedback cycles, stable, mature and battle-hardened software tools, well documented workflows and automation of mundane tasks. An application must be comprehensible and its behaviour transparent; to this end, automated testing, error reporting, logging, and metrics reporting are invaluable tools.</p>
          <p>A crucial fact in software development: Code is much, much harder to read than it is to write. Therefore, documentation, readability and maintainability are extremely important to me. Not only for the code we write, but also for the libraries we depend on. I prefer to utilize established, mature tools that work and do not surprise the user, rather than experimenting with the latest fad of the week. This approach allows developers to get the job done and keep focus on higher levels of abstraction.</p>
          <p>I am an avid Linux user who lives in the terminal. I invest a lot of time to learn the ins and outs of the tools I use regularly. For example, I use a <a href="https://en.wikipedia.org/wiki/Tiling_window_manager">tiling window manager</a> and have learned most common operations in muscle memory, as well as the majority of the keybindings in my text editor. I find these skills essential in order to <a href="https://www.joelonsoftware.com/2007/06/05/smart-and-gets-things-done/">get things done</a> effectively and leave mental capacity for higher level ideas.</p>
          <p>I am patient and humble, and a reliable team player. I have a strong sense of fairness and value good reasoning over arguments from authority. Being an introvert and analytical by nature, I perform best on problems that are given due time for focus, deep work and reflection. An effective workplace needs to balance the need of close collaboration versus focused work, and I will not thrive in an environment which focuses solely on either extreme of that scale.</p>
        </section>

        <h2>Core skills</h2>

        <section>
          <p>My core technological skills revolve around Python and web programming. Here's a summary of the tools and technologies I'm skilled in and use regularly:</p>
          <div class="keywords">
            <dl>
              <dt>Desktop environment:</dt>
              <dd><a href="https://archlinux.org">Arch Linux</a>, <a href="https://swaywm.org">Sway</a>, <a href="https://alacritty.org">Alacritty</a>, <a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a>, <a href="https://code.visualstudio.com">VS Code</a>, <a href="https://www.sublimetext.com">Sublime Text</a>, <a href="https://www.vim.org">vim</a>, <a href="https://obsidian.md">Obsidian</a>. (See also my <a href="https://github.com/kvikshaug/dotfiles">public dotfiles</a>)</dd>
            </dl>
            <dl>
              <dt>Web development:</dt>
              <dd><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP">HTTP</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/HTML">HTML</a>, <a href="https://www.ecma-international.org/publications-and-standards/standards/ecma-262/">JavaScript</a>, <a href="https://vuejs.org">Vue</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/CSS">CSS</a>, <a href="https://sass-lang.com/documentation/syntax">Sass</a>, <a href="https://nginx.org">nginx</a>, <a href="https://gunicorn.org">gunicorn</a>, <a href="https://www.haproxy.org">HAProxy</a>.</dd>
            </dl>
            <dl>
              <dt>Programming:</dt>
              <dd><a href="https://www.python.org">Python</a>, <a href="https://openjdk.java.net">Java</a>, <a href="https://scala-lang.org">Scala</a>, <a href="https://flask.palletsprojects.com">Flask</a>, <a href="https://www.djangoproject.com">Django</a>, <a href="https://www.postgresql.org">PostgreSQL</a>, <a href="https://www.sqlalchemy.org">SQLAlchemy</a>, <a href="https://www.rabbitmq.com">RabbitMQ</a>, <a href="https://bitcoin.org/bitcoin.pdf">cryptocurrency/blockchain</a>.</dd>
            </dl>
            <dl>
              <dt>DevOps and SRE:</dt>
              <dd><a href="https://git-scm.com">Git</a>, <a href="https://www.docker.com">Docker</a>, <a href="https://kubernetes.io">Kubernetes</a>, <a href="https://travis-ci.org/">Travis CI</a>, <a href="https://letsencrypt.org">Let's Encrypt</a>, <a href="https://prometheus.io">Prometheus</a>/<a href="https://grafana.com">Grafana</a>, <a href="https://sentry.io">Sentry</a>, <a href="https://marketingplatform.google.com/about/analytics/">Google Analytics</a>, <a href="https://cloud.google.com">Google Cloud</a>, <a href="https://aws.amazon.com">AWS</a>.</dd>
            </dl>
            <dl>
              <dt>Collaboration</dt>
              <dd><a href="https://trello.com">Trello</a>, <a href="https://slack.com">Slack</a>, <a href="https://www.figma.com">Figma</a>, <a href="https://weblate.org">Weblate</a>, <a href="https://nextcloud.com">Nextcloud</a>.</dd>
            </dl>
            <dl>
              <dt>Languages I'm interested in exploring:</dt>
              <dd><a href="https://www.rust-lang.org">Rust</a>, <a href="https://go.dev">Go</a>, <a href="https://julialang.org">Julia</a>.</dd>
            </dl>
          </div>
        </section>
    <article class="quote theme-light-accent">
      <div class="cell left">
        <blockquote>You must not be afraid of playing wrong notes. Just forget it, play it wrong! But play!</blockquote>
        <div class="author">—Alan Watts</div>
      </div>
      <div class="cell right paragraphs">
        <p title="I don't write a blog, or otherwise publish new content regularly. And so, you're unlikely to gain much from visiting my site regularly. Therefore, before you leave, I'd like to share the single most important thing I've learned.">I'd like to share one idea with you.</p>
        <hr>
        <p><strong>Your mind dictates the quality of every moment of your experience.</strong></p>
        <p>And yet, many go through life ignorant of what determines the character of their minds. You are not a <em>thinker</em> of your thoughts or an <em>experiencer</em> of your experience; the <em>self</em> you identify with is an illusion.</p>
        <p>The mind is all you have, and analogous to muscles, it can (and should) be trained. Through the practice of meditation you can discover the unintuitive facts about how it works.</p>
        <p>By realizing that I was lost in thought, I have learned to be <em>present</em>. To <em>play</em>.</p>
        <hr>
        <p>If this idea interest you, I recommend reading <a href="https://www.samharris.org/books/waking-up">Waking Up</a> by Sam Harris and trying its related <a href="https://www.wakingup.com/">meditation app</a>.</p>
      </div>
    </article>

    <footer id="contact" class="theme brown-dark" data-trigger-nav="contact">
    <footer class="theme-dark">
      <div class="container content">
        <div>
        <div class="paragraphs">
          <p>To get in touch, drop me a line at <a href="mailto:ali@kvikshaug.no">ali@kvikshaug.no</a>.</p>
          <p>You can also find me on IRC with nickname <em title="Today I mostly go by the nickname kvikshaug, but on IRC I'm still known by my earlier nickname murr4y.">murr4y</em> on networks like <a href="http://www.efnet.org/">efnet</a>, <a href="https://libera.chat/">libera</a> and <a href="http://www.slashnet.org/">slashnet</a>.</p>
          <p>To send me an encrypted message or verify something I signed, use my public PGP key <a href="/assets/0030199E.asc" title="Full fingerprint: 3624 317D 5BD2 01F8 E93D  9BD1 916A 7833 0030 199E">0030199E</a>.</p>
        </div>
        <div>
        <div class="paragraphs">
          <p>To send me an encrypted message or verify something I signed, use my public PGP key <a href="/assets/0030199E.asc"><code>3624 317D 5BD2 01F8 E93D 9BD1 916A 7833 0030 199E</code></a>.</p>
          <p>Owe me a beer? Send bitcoins to: <code>3AXRXhryGHZ2hgdc1hTSMd9ewdVKruS9NB</code></p>
          <img class="qr" src="/assets/images/bitcoin-qr.png" width="200" height="200" alt="3AXRXhryGHZ2hgdc1hTSMd9ewdVKruS9NB" title="Buy me a beer with bitcoins: 3AXRXhryGHZ2hgdc1hTSMd9ewdVKruS9NB">
        </div>
      </div>

      <div id="guestbook" class="container guestbook">
        <h2>Leave a message in my guestbook!</h2>
        <div class="messages">
          {% for message in guestbook %}
            <div class="message">
              <div>{{ message["message"] | replace("\n", "<br>" | safe) }}</div>
              <em>{{ message["datetime"].strftime("%Y-%m-%d %H:%M") }}</em>
            </div>
            {% if not loop.last %}
              <hr>
            {% endif %}
          {% endfor %}
        </div>
        <form action="/guestbook" method="post" class="form">
          <textarea name="message" id="message" rows="5" placeholder="I often wonder who stumbles upon my website. Who are you and what would you like to say?"></textarea>
          <button type="submit">Leave message</button>
        </form>
      </div>

      <div class="container">

M tests/integration/test_endpoints.py => tests/integration/test_endpoints.py +0 -10
@@ 1,13 1,3 @@
def test_home(client):
    response = client.get("/")
    assert response.status_code == 200


def test_projects(client):
    response = client.get("/projects")
    assert response.status_code == 200


def test_profile(client):
    response = client.get("/profile")
    assert response.status_code == 200