diff --git a/gatsby/.eslintrc.js b/gatsby/.eslintrc.js
index 3725e19..1e0b5ab 100644
--- a/gatsby/.eslintrc.js
+++ b/gatsby/.eslintrc.js
@@ -22,5 +22,6 @@ module.exports = {
     'semi': 1,
     'indent': ['warn', 2],
     'comma-dangle': ['warn', 'always-multiline'],
+    'no-unused-vars': 1,
   },
 };
diff --git a/gatsby/gatsby-node.js b/gatsby/gatsby-node.js
index 1dd3f84..62f46bb 100644
--- a/gatsby/gatsby-node.js
+++ b/gatsby/gatsby-node.js
@@ -67,11 +67,11 @@ exports.onCreateNode = async function ({ node, getNode, actions }) {
 exports.createPages = async ({ graphql, actions, reporter }) => {
   const { createPage } = actions;
   // get all images
-  const galleryImages = await graphql(
-    `
+  const galleryImages = await graphql(`
       {
         allFile(filter: {
-          sourceInstanceName: { eq: "gallery" }}) {
+          sourceInstanceName: { eq: "gallery" }}
+        ) {
           edges {
             node {
               relativePath,
@@ -80,8 +80,7 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
           }
         }
       }
-    `
-  );
+    `);
   // Handle errors
   if (galleryImages.errors) {
     reporter.panicOnBuild('Error while running GraphQL query.');
diff --git a/gatsby/package.json b/gatsby/package.json
index 299aacd..f6551b1 100644
--- a/gatsby/package.json
+++ b/gatsby/package.json
@@ -35,13 +35,15 @@
     "node-iptc": "^1.0.5",
     "postcss": "^8.3.4",
     "postcss-nested": "^5.0.5",
+    "ramda": "^0.27.1",
     "react": "^17.0.1",
     "react-dom": "^17.0.1",
     "react-helmet": "^6.1.0",
     "react-responsive-masonry": "^2.1.2",
     "sass": "^1.34.0",
     "styled-components": "^5.3.0",
-    "tailwindcss": "^2.1.2"
+    "tailwindcss": "^2.1.2",
+    "use-breakpoint": "^2.0.1"
   },
   "devDependencies": {
     "babel-eslint": "^10.1.0",
diff --git a/gatsby/src/components/GalleryImage.js b/gatsby/src/components/GalleryImage.js
index 0435d26..14115c7 100644
--- a/gatsby/src/components/GalleryImage.js
+++ b/gatsby/src/components/GalleryImage.js
@@ -1,13 +1,13 @@
 import React from 'react';
 import { graphql } from 'gatsby';
-import { getMeta, getName, hasName } from '../utils';
+import { getAspectRatio, getMeta, getName, hasName } from '../utils';
 import { GatsbyImage, getImage } from 'gatsby-plugin-image';
 import { Helmet } from 'react-helmet';
 import classnames from 'classnames';
 
 const GalleryImage = ({ data }) => {
   const image = data.allFile.edges[0].node;
-  const ar = image.childImageSharp.fluid.aspectRatio;
+  const ar = getAspectRatio(image);
   console.log(ar);
   // const imageStyle = {}
   // if (ar > 1) {
@@ -22,7 +22,7 @@ const GalleryImage = ({ data }) => {
   return (<>
     <Helmet>
       <title>{name} - Gallery | Chuck Dries</title>
-      <body className="bg-black" />
+      <body className="bg-black text-white" />
     </Helmet>
     <div className="min-h-screen flex flex-col justify-center">
       {/* TODO: change layout by amount of empty space on side of page, not aspect ratio? */}
@@ -95,4 +95,4 @@ export const query = graphql`
 
 `;
 
-export default GalleryImage;
\ No newline at end of file
+export default GalleryImage;
diff --git a/gatsby/src/components/MasonryGallery.js b/gatsby/src/components/MasonryGallery.js
new file mode 100644
index 0000000..899085b
--- /dev/null
+++ b/gatsby/src/components/MasonryGallery.js
@@ -0,0 +1,68 @@
+import * as React from 'react';
+import { Link } from 'gatsby';
+import { GatsbyImage, getImage } from 'gatsby-plugin-image';
+import * as R from 'ramda';
+import { getAspectRatio, getName } from '../utils';
+// TODO: use resolveCOnfig to not need to define screens in theme file
+import resolveConfig from 'tailwindcss/resolveConfig';
+import tailwindConfig from '../../tailwind.config.js';
+import useBreakpoint from 'use-breakpoint';
+
+const {theme: {screens}} = resolveConfig(tailwindConfig);
+const themeBreakpoints = R.map(size => parseInt(size, 10), screens);
+console.log(themeBreakpoints);
+
+const MasonryGallery = ({ images, itemsPerRow: itemsPerRowByBreakpoint }) => {
+  const breakpoints = React.useMemo(() => 
+    R.pick(R.keys(itemsPerRowByBreakpoint), themeBreakpoints)
+  , [itemsPerRowByBreakpoint]);
+
+  const { breakpoint, maxWidth, minWidth } = useBreakpoint(breakpoints, 'md');
+
+  const aspectRatios = React.useMemo(() => R.map(getAspectRatio, images), [images]);
+  const rowAspectRatioSumsByBreakpoint = React.useMemo(() => R.map(R.pipe(
+    R.splitEvery(R.__, aspectRatios),
+    R.map(R.sum)
+  ))(itemsPerRowByBreakpoint), [aspectRatios, itemsPerRowByBreakpoint]);
+
+  // console.log('bp', breakpoint);
+  const rowAspectRatioSumsForCurrentBP = rowAspectRatioSumsByBreakpoint[breakpoint];
+  console.log('rowAspectRatioSumsForCurrentBP :', rowAspectRatioSumsForCurrentBP);
+  
+  return (
+    <div
+      style={{
+        width: '100%',
+        position: 'relative',
+      }}
+      // className='mx-auto'
+      // style={{ maxWidth: minWidth }}
+    >
+      {images.map((image, i) => {
+        const rowIndex = Math.floor(i / itemsPerRowByBreakpoint[breakpoint]);
+        const rowAspectRatioSum = rowAspectRatioSumsForCurrentBP[rowIndex];
+        // console.log('ars', rowAspectRatioSum);
+        if (i === 0) {
+          console.log(rowIndex, rowAspectRatioSum);
+          console.log(getName(image), `${(getAspectRatio(image) / rowAspectRatioSum) * 100}%`);
+        }
+        return (
+          // <Link className='inline-block' key={image.base} state={{modal: true}} to={`/photogallery/${image.base}`}>
+          <GatsbyImage
+            key={`${image.base}-img`}
+            className='inline-block'
+            style={{
+              width: `${(getAspectRatio(image) / rowAspectRatioSum) * 100}%`,
+            }}
+            // objectFit='contain'
+            image={getImage(image)}
+            alt={getName(image)}
+          />
+          // </Link>
+        );
+      })}
+    </div>);
+  // return null;
+};
+
+export default MasonryGallery;
diff --git a/gatsby/src/pages/index.js b/gatsby/src/pages/index.js
index 602ff89..e574225 100644
--- a/gatsby/src/pages/index.js
+++ b/gatsby/src/pages/index.js
@@ -8,7 +8,7 @@ const IndexPage = ({ data }) => {
   console.log('images', images);
 
   return (
-    <main className="font-serif">
+    <main className="font-serif text-white">
       <section style={{ height: '50vh' }} className="m-2 py-6 intro flex flex-col justify-center flex-auto bg-black rounded-xl">
         <div className="mx-auto px-4 md:px-2 w-full md:w-8 ">
           <h1 className="italic font-normal text-5xl text-pink-500">Chuck Dries</h1>
@@ -61,7 +61,9 @@ const IndexPage = ({ data }) => {
 export const query = graphql`
 query HomePageGallery {
   allFile(filter: {
-    sourceInstanceName: { eq: "gallery" }}) {
+    sourceInstanceName: { eq: "gallery" }}
+    sort: {order: DESC, fields: childrenImageSharp___fields___imageMeta___dateTaken}
+  ) {
     edges {
       node {
       	relativePath,
diff --git a/gatsby/src/pages/photogallery.js b/gatsby/src/pages/photogallery.js
index c0585a8..36260e9 100644
--- a/gatsby/src/pages/photogallery.js
+++ b/gatsby/src/pages/photogallery.js
@@ -1,59 +1,47 @@
 import * as React from 'react';
-import { graphql, Link } from 'gatsby';
-import { GatsbyImage, getImage } from 'gatsby-plugin-image';
+import { graphql } from 'gatsby';
+// import { GatsbyImage, getImage } from 'gatsby-plugin-image';
 import { Helmet } from 'react-helmet';
-import Masonry, { ResponsiveMasonry } from 'react-responsive-masonry';
+// import Masonry, { ResponsiveMasonry } from 'react-responsive-masonry';
 
-import { getMeta } from '../utils';
+// import { getMeta } from '../utils';
+import MasonryGallery from '../components/MasonryGallery';
 
 const GalleryPage = ({ data }) => {
   const images = React.useMemo(() =>
     data.allFile.edges
       .map(edge => edge.node, [data])
-      .sort((left, right) => {
-        const leftDate = new Date(getMeta(left).dateTaken);
-        console.log(leftDate);
-        const rightDate = new Date(getMeta(right).dateTaken);
-        if (leftDate < rightDate) {
-          return 1;
-        }
-        if (leftDate > rightDate) {
-          return -1;
-        }
-        return 0;
-      }) // TODO HERE
+      // .sort((left, right) => {
+      //   const leftDate = new Date(getMeta(left).dateTaken);
+      //   console.log(leftDate);
+      //   const rightDate = new Date(getMeta(right).dateTaken);
+      //   if (leftDate < rightDate) {
+      //     return 1;
+      //   }
+      //   if (leftDate > rightDate) {
+      //     return -1;
+      //   }
+      //   return 0;
+      // }) // TODO HERE
   , [data]);
 
   return (<>
     <Helmet>
       <title>Gallery | Chuck Dries</title>
-      <body className="bg-black" />
+      <body className="bg-black text-white" />
     </Helmet>
     <div className="bg-black min-h-screen">
       <h1 className="text-2xl">Gallery</h1>
       <div className="mx-auto" style={{maxWidth: '1800px'}}>
-        {/* TODO swap masonry plugin, this one makes really unbalanced columns */}
-        {/* ...implement manually :sadge: */}
-        <ResponsiveMasonry
-          columnsCountBreakPoints={{ 350: 1, 650: 2, 1200: 3 }}
-        >
-          <Masonry gutter='5px'>
-            {images.map(image => {
-              console.log('ar', image.childImageSharp);
-              const name = getMeta(image).iptc.object_name || image.base;
-              return (
-                <React.Fragment key={name}>
-                  <Link state={{modal: true}} to={`/photogallery/${image.base}`}>
-                    <GatsbyImage
-                      key={image.base}
-                      image={getImage(image)}
-                      alt={name} />
-                  </Link>
-                </React.Fragment>
-              );
-            })}
-          </Masonry>
-        </ResponsiveMasonry>
+        <MasonryGallery
+          images={images}
+          itemsPerRow={{
+            sm: 2,
+            md: 3,
+            lg: 4,
+            xl: 5,
+          }}
+        />
       </div>
     </div>
   </>);
@@ -62,7 +50,9 @@ const GalleryPage = ({ data }) => {
 export const query = graphql`
 query GalleryPageQuery {
   allFile(filter: {
-    sourceInstanceName: { eq: "gallery" }}) {
+    sourceInstanceName: { eq: "gallery" }}
+    sort: {order: DESC, fields: childrenImageSharp___fields___imageMeta___dateTaken}
+  ) {
     edges {
       node {
       	relativePath,
@@ -73,7 +63,7 @@ query GalleryPageQuery {
           },
           gatsbyImageData(
             layout: CONSTRAINED,
-            width: 650
+            height: 400
           )
           fields {
             imageMeta {
diff --git a/gatsby/src/utils.js b/gatsby/src/utils.js
index e88c3a0..debb0b3 100644
--- a/gatsby/src/utils.js
+++ b/gatsby/src/utils.js
@@ -2,4 +2,6 @@ export const getMeta = (image) => image.childImageSharp.fields.imageMeta;
 
 export const getName = (image) => getMeta(image)?.iptc.object_name || image.base;
 
-export const hasName = (image) => Boolean(getMeta(image)?.iptc.object_name);
\ No newline at end of file
+export const hasName = (image) => Boolean(getMeta(image)?.iptc.object_name);
+
+export const getAspectRatio = (image) => image.childImageSharp.fluid.aspectRatio;
diff --git a/gatsby/tailwind.config.js b/gatsby/tailwind.config.js
index dce82a5..bc4915b 100644
--- a/gatsby/tailwind.config.js
+++ b/gatsby/tailwind.config.js
@@ -1,9 +1,16 @@
-const defaultTheme = require('tailwindcss/defaultTheme')
+const defaultTheme = require('tailwindcss/defaultTheme');
 
 module.exports = {
   purge: ['./src/**/*.{js,jsx,ts,tsx}'],
   // darkMode: 'media', // or 'media' or 'class'
   theme: {
+    screens: {
+      'sm': '640px',
+      'md': '768px',
+      'lg': '1024px',
+      'xl': '1280px',
+      '2xl': '1536px',
+    },
     spacing: {
       '1': '8px',
       '2': '12px',
@@ -12,12 +19,12 @@ module.exports = {
       '5': '32px',
       '6': '48px',
       '7': '80px',
-      '8': '800px'
+      '8': '800px',
     },
     fontFamily: {
       ...defaultTheme.fontFamily,
       // serif: ['Didot', 'Didot LT', 'STD', 'Hoefler Text' , 'Garamond', 'Times New Roman', 'serif']
-      serif: ['Playfair Display', 'serif']
+      serif: ['Playfair Display', 'serif'],
     },
     extend: {},
   },
@@ -25,4 +32,4 @@ module.exports = {
     extend: {},
   },
   plugins: [],
-}
+};
diff --git a/gatsby/yarn.lock b/gatsby/yarn.lock
index 82ec0cf..fa510cd 100644
--- a/gatsby/yarn.lock
+++ b/gatsby/yarn.lock
@@ -10973,6 +10973,11 @@ quick-lru@^5.1.1:
   resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
   integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
 
+ramda@^0.27.1:
+  version "0.27.1"
+  resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9"
+  integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==
+
 randombytes@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -13616,6 +13621,11 @@ url@^0.11.0:
     punycode "1.3.2"
     querystring "0.2.0"
 
+use-breakpoint@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/use-breakpoint/-/use-breakpoint-2.0.1.tgz#0e98cd55a6b0173147b84837a8cdecc63eabfcd7"
+  integrity sha512-f9PuRHzPsCIBW6oizdGpTAuH+49Tt9hMvByWQYpqI95tUN8k6qDX5WhL5s3b6cQfbubtIHCd1bw/8s5CzSbXpA==
+
 use@^3.1.0:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"