run prettier

This commit is contained in:
Chuck Dries 2021-07-12 18:54:08 -07:00
parent 5df4c2baf4
commit d8655904fa
31 changed files with 1183 additions and 917 deletions

View File

@ -2,24 +2,24 @@ module.exports = {
globals: { globals: {
__PATH_PREFIX__: true, __PATH_PREFIX__: true,
}, },
'parser': 'babel-eslint', // uses babel-eslint transforms parser: "babel-eslint", // uses babel-eslint transforms
'settings': { settings: {
'react': { react: {
'version': 'detect', // detect react version version: "detect", // detect react version
}, },
}, },
'env': { env: {
'node': true, // defines things like process.env when generating through node node: true, // defines things like process.env when generating through node
'browser': true browser: true,
}, },
'extends': [ extends: [
'eslint:recommended', // use recommended configs "eslint:recommended", // use recommended configs
'plugin:react/recommended', "plugin:react/recommended",
'plugin:react-hooks/recommended', "plugin:react-hooks/recommended",
], ],
'rules': { rules: {
'react/prop-types': 0, "react/prop-types": 0,
'no-unused-vars': 1, "no-unused-vars": 1,
'react/jsx-sort-props': 1, "react/jsx-sort-props": 1,
}, },
}; };

View File

@ -6,9 +6,9 @@ name: deploy
# events but only for the master branch # events but only for the master branch
on: on:
push: push:
branches: [ master ] branches: [master]
pull_request: pull_request:
branches: [ master ] branches: [master]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel # A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs: jobs:
@ -18,24 +18,24 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job # Steps represent a sequence of tasks that will be executed as part of the job
steps: steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set Node.js - name: Set Node.js
uses: actions/setup-node@master uses: actions/setup-node@master
with: with:
node-version: 16.x node-version: 16.x
- name: Install dependencies - name: Install dependencies
run: yarn install --prod --pure-lockfile run: yarn install --prod --pure-lockfile
- name: Lint - name: Lint
run: yarn run lint run: yarn run lint
- name: Build - name: Build
run: yarn run build run: yarn run build
- name: upload - name: upload
uses: burnett01/rsync-deployments@4.1 uses: burnett01/rsync-deployments@4.1
with: with:
switches: -zr --delete --exclude node_modules --exclude '.git*' switches: -zr --delete --exclude node_modules --exclude '.git*'
path: ./public/ path: ./public/
remote_path: www/personal-website remote_path: www/personal-website
remote_host: droplet.chuckdries.com remote_host: droplet.chuckdries.com
remote_user: ${{ secrets.CI_USER }} remote_user: ${{ secrets.CI_USER }}
remote_key: ${{ secrets.CI_SSH_KEY }} remote_key: ${{ secrets.CI_SSH_KEY }}

133
.vscode/launch.json vendored
View File

@ -1,71 +1,68 @@
{ {
"version": "0.2.0", "version": "0.2.0",
"configurations": [{ "configurations": [
"name": "Launch Chrome", {
"type": "chrome", "name": "Launch Chrome",
"request": "launch", "type": "chrome",
"url": "http://localhost:3000", "request": "launch",
"sourceMaps": true, "url": "http://localhost:3000",
"webRoot": "${workspaceRoot}" "sourceMaps": true,
}, "webRoot": "${workspaceRoot}"
{ },
"name": "Launch Unix", {
"type": "node", "name": "Launch Unix",
"request": "launch", "type": "node",
"program": "/usr/local/bin/lite-server", "request": "launch",
"stopOnEntry": false, "program": "/usr/local/bin/lite-server",
"args": [], "stopOnEntry": false,
"cwd": "${workspaceRoot}", "args": [],
"preLaunchTask": null, "cwd": "${workspaceRoot}",
"runtimeExecutable": null, "preLaunchTask": null,
"runtimeArgs": [ "runtimeExecutable": null,
"--nolazy" "runtimeArgs": ["--nolazy"],
], "env": {
"env": { "NODE_ENV": "development"
"NODE_ENV": "development" },
}, "externalConsole": false,
"externalConsole": false, "sourceMaps": false,
"sourceMaps": false, "outDir": null
"outDir": null },
}, {
{ "name": "Launch on Windows",
"name": "Launch on Windows", "type": "node",
"type": "node", "request": "launch",
"request": "launch", "program": "C:\\Users\\chuckdries\\AppData\\Roaming\\npm\\node_modules\\lite-server\\index.js",
"program": "C:\\Users\\chuckdries\\AppData\\Roaming\\npm\\node_modules\\lite-server\\index.js", "stopOnEntry": false,
"stopOnEntry": false, "args": [],
"args": [], "cwd": "${workspaceRoot}",
"cwd": "${workspaceRoot}", "preLaunchTask": null,
"preLaunchTask": null, "runtimeExecutable": null,
"runtimeExecutable": null, "runtimeArgs": ["--nolazy"],
"runtimeArgs": [ "env": {
"--nolazy" "NODE_ENV": "development"
], },
"env": { "console": "internalConsole",
"NODE_ENV": "development" "sourceMaps": true,
}, "outDir": null
"console": "internalConsole", },
"sourceMaps": true,
"outDir": null
},
{ {
"name": "Attach Chrome", "name": "Attach Chrome",
"type": "chrome", "type": "chrome",
"request": "attach", "request": "attach",
"port": 9222 "port": 9222
}, },
{ {
"name": "Attach", "name": "Attach",
"type": "node", "type": "node",
"request": "attach", "request": "attach",
"port": 3000, "port": 3000,
"address": "localhost", "address": "localhost",
"restart": false, "restart": false,
"sourceMaps": false, "sourceMaps": false,
"outDir": null, "outDir": null,
"localRoot": "${workspaceRoot}", "localRoot": "${workspaceRoot}",
"remoteRoot": null "remoteRoot": null
} }
] ]
} }

View File

@ -1,3 +1,3 @@
{ {
"python.linting.pylintEnabled": false "python.linting.pylintEnabled": false
} }

28
.vscode/tasks.json vendored
View File

@ -1,16 +1,16 @@
{ {
// See https://go.microsoft.com/fwlink/?LinkId=733558 // See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format // for the documentation about the tasks.json format
"version": "2.0.0", "version": "2.0.0",
"command": "gulp", "command": "gulp",
"tasks": [ "tasks": [
{ {
"label": "default", "label": "default",
"type": "gulp", "type": "gulp",
"task": "default", "task": "default",
"isBackground": true, "isBackground": true,
"problemMatcher": [], "problemMatcher": [],
"group": "build" "group": "build"
} }
] ]
} }

View File

@ -1,10 +1,12 @@
- Gallery - Gallery
- [x] Custom image metadata - [x] Custom image metadata
- [ ] add metadata to image files - [ ] add metadata to image files
- [ ] image details page (modal route?) - [ ] image details page (modal route?)
- [ ] gallery page - [ ] gallery page
- Homepage - Homepage
- [ ] homepage basic layout - [ ] homepage basic layout
- [ ] homepage aesthetics - [ ] homepage aesthetics
- [ ] homepage to gallery page transition - [ ] homepage to gallery page transition
@ -14,6 +16,7 @@
- [ ] About page? - [ ] About page?
- Portfolio/Projects page - Portfolio/Projects page
- basic layout - basic layout
- source data - source data
- aesthetics - aesthetics

View File

@ -1,17 +1,17 @@
import './src/styles/global.css'; import "./src/styles/global.css";
import posthog from 'posthog-js'; import posthog from "posthog-js";
const env = process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || 'development'; const env =
if (env === 'production') { process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || "development";
posthog.init('HR8Gte105aCHNx2BqhL1XkbvH9kzKGptxjkbhuTj6Ek', { api_host: 'https://posthog.chuckdries.com' }); if (env === "production") {
posthog.init("HR8Gte105aCHNx2BqhL1XkbvH9kzKGptxjkbhuTj6Ek", {
api_host: "https://posthog.chuckdries.com",
});
} }
export const onRouteUpdate = function () { export const onRouteUpdate = function () {
if ( if (env === "production" && typeof window.plausible === "object") {
env === 'production' && window.plausible("pageview");
typeof window.plausible === 'object' posthog.capture("$pageview");
) {
window.plausible('pageview');
posthog.capture('$pageview');
} }
}; };
// import * as React from 'react'; // import * as React from 'react';

View File

@ -1,56 +1,56 @@
module.exports = { module.exports = {
siteMetadata: { siteMetadata: {
title: 'Chuck Dries', title: "Chuck Dries",
siteUrl: 'https://chuckdries.com', siteUrl: "https://chuckdries.com",
}, },
plugins: [ plugins: [
'gatsby-plugin-sass', "gatsby-plugin-sass",
'gatsby-plugin-image', "gatsby-plugin-image",
'gatsby-plugin-react-helmet', "gatsby-plugin-react-helmet",
{ {
resolve: 'gatsby-plugin-manifest', resolve: "gatsby-plugin-manifest",
options: { options: {
icon: 'src/images/glasses-outline.svg', icon: "src/images/glasses-outline.svg",
}, },
}, },
'gatsby-plugin-mdx', "gatsby-plugin-mdx",
'gatsby-plugin-sharp', "gatsby-plugin-sharp",
'gatsby-transformer-sharp', "gatsby-transformer-sharp",
'gatsby-plugin-postcss', "gatsby-plugin-postcss",
{ {
resolve: 'gatsby-source-filesystem', resolve: "gatsby-source-filesystem",
options: { options: {
name: 'images', name: "images",
path: './src/images/', path: "./src/images/",
}, },
__key: 'images', __key: "images",
}, },
{ {
resolve: 'gatsby-source-filesystem', resolve: "gatsby-source-filesystem",
options: { options: {
name: 'gallery', name: "gallery",
path: './data/gallery/', path: "./data/gallery/",
}, },
__key: 'gallery', __key: "gallery",
}, },
{ {
resolve: 'gatsby-source-filesystem', resolve: "gatsby-source-filesystem",
options: { options: {
name: 'pages', name: "pages",
path: './src/pages/', path: "./src/pages/",
}, },
__key: 'pages', __key: "pages",
}, },
{ {
resolve: 'gatsby-plugin-eslint', resolve: "gatsby-plugin-eslint",
options: { options: {
stages: ['develop'], stages: ["develop"],
extensions: ['js', 'jsx'], extensions: ["js", "jsx"],
exclude: ['node_modules', '.cache', 'public'], exclude: ["node_modules", ".cache", "public"],
// Any eslint-webpack-plugin options below // Any eslint-webpack-plugin options below
}, },
}, },
'gatsby-plugin-preval', "gatsby-plugin-preval",
'gatsby-plugin-robots-txt', "gatsby-plugin-robots-txt",
], ],
}; };

View File

@ -1,12 +1,12 @@
const fs = require('fs'); const fs = require("fs");
const util = require('util'); const util = require("util");
const path = require('path'); const path = require("path");
const { read } = require('fast-exif'); const { read } = require("fast-exif");
const iptc = require('node-iptc'); const iptc = require("node-iptc");
const Vibrant = require('node-vibrant'); const Vibrant = require("node-vibrant");
const chroma = require('chroma-js'); const chroma = require("chroma-js");
const chalk = require('chalk'); const chalk = require("chalk");
const R = require('ramda'); const R = require("ramda");
const readFile = util.promisify(fs.readFile); const readFile = util.promisify(fs.readFile);
@ -15,9 +15,11 @@ const badContrast = (color1, color2) => chroma.contrast(color1, color2) < 4.5;
const logColorsWithContrast = (color1, color2, text) => { const logColorsWithContrast = (color1, color2, text) => {
const c1hex = color1.hex(); const c1hex = color1.hex();
const c2hex = color2.hex(); const c2hex = color2.hex();
console.log(chalk.hex(c1hex).bgHex(c2hex)( console.log(
`${text} ${c1hex}/${c2hex} ${chroma.contrast(color1, color2)}` chalk.hex(c1hex).bgHex(c2hex)(
)); `${text} ${c1hex}/${c2hex} ${chroma.contrast(color1, color2)}`
)
);
}; };
function processColors(vibrantData, imagePath) { function processColors(vibrantData, imagePath) {
@ -29,7 +31,10 @@ function processColors(vibrantData, imagePath) {
let LightMuted = chroma(vibrantData.LightMuted.getRgb()); let LightMuted = chroma(vibrantData.LightMuted.getRgb());
// first pass - darken bg and lighten relevant fg colors // first pass - darken bg and lighten relevant fg colors
if (badContrast(DarkVibrant, Vibrant) || badContrast(DarkVibrant, LightMuted)) { if (
badContrast(DarkVibrant, Vibrant) ||
badContrast(DarkVibrant, LightMuted)
) {
DarkVibrant = DarkVibrant.darken(); DarkVibrant = DarkVibrant.darken();
if (badContrast(DarkVibrant, Vibrant)) { if (badContrast(DarkVibrant, Vibrant)) {
Vibrant = Vibrant.brighten(); Vibrant = Vibrant.brighten();
@ -39,7 +44,10 @@ function processColors(vibrantData, imagePath) {
} }
} }
// second pass - first doesn't always do enough // second pass - first doesn't always do enough
if (badContrast(DarkVibrant, Vibrant) || badContrast(DarkVibrant, LightMuted)) { if (
badContrast(DarkVibrant, Vibrant) ||
badContrast(DarkVibrant, LightMuted)
) {
// DarkVibrant = DarkVibrant.darken(); // DarkVibrant = DarkVibrant.darken();
if (badContrast(DarkVibrant, Vibrant)) { if (badContrast(DarkVibrant, Vibrant)) {
Vibrant = Vibrant.brighten(2); Vibrant = Vibrant.brighten(2);
@ -49,16 +57,15 @@ function processColors(vibrantData, imagePath) {
} }
} }
if (badContrast(DarkVibrant, Vibrant)){ if (badContrast(DarkVibrant, Vibrant)) {
console.warn('contrast still too low', imagePath); console.warn("contrast still too low", imagePath);
logColorsWithContrast(Vibrant, DarkVibrant, 'V-DV'); logColorsWithContrast(Vibrant, DarkVibrant, "V-DV");
} }
if (badContrast(DarkVibrant, LightMuted)){ if (badContrast(DarkVibrant, LightMuted)) {
console.warn('contrast still too low', imagePath); console.warn("contrast still too low", imagePath);
logColorsWithContrast(LightMuted, DarkVibrant, 'LM-DV'); logColorsWithContrast(LightMuted, DarkVibrant, "LM-DV");
} }
return { return {
Vibrant: Vibrant.rgb(), Vibrant: Vibrant.rgb(),
DarkVibrant: DarkVibrant.rgb(), DarkVibrant: DarkVibrant.rgb(),
@ -82,25 +89,20 @@ function transformMetaToNodeData(exifData, iptcData, vibrantData, imagePath) {
const gps = { longitude: null, latitude: null }; const gps = { longitude: null, latitude: null };
if (exifData) { if (exifData) {
if ( if (exifData.gps && exifData.gps.GPSLongitude && exifData.gps.GPSLatitude) {
exifData.gps &&
exifData.gps.GPSLongitude &&
exifData.gps.GPSLatitude
) {
gps.longitude = convertDMSToDD( gps.longitude = convertDMSToDD(
exifData.gps.GPSLongitude, exifData.gps.GPSLongitude,
exifData.gps.GPSLongitudeRef === 'E' exifData.gps.GPSLongitudeRef === "E"
); );
gps.latitude = convertDMSToDD( gps.latitude = convertDMSToDD(
exifData.gps.GPSLatitude, exifData.gps.GPSLatitude,
exifData.gps.GPSLatitudeRef === 'N' exifData.gps.GPSLatitudeRef === "N"
); );
} }
} }
const vibrant = vibrantData ? processColors(vibrantData, imagePath) : null; const vibrant = vibrantData ? processColors(vibrantData, imagePath) : null;
return { return {
exif: exifData?.exif, exif: exifData?.exif,
gps, gps,
@ -110,10 +112,9 @@ function transformMetaToNodeData(exifData, iptcData, vibrantData, imagePath) {
}; };
} }
exports.onCreateNode = async function ({ node, actions }) { exports.onCreateNode = async function ({ node, actions }) {
const { createNodeField } = actions; const { createNodeField } = actions;
if (node.internal.type === 'File' && node.sourceInstanceName === 'gallery') { if (node.internal.type === "File" && node.sourceInstanceName === "gallery") {
const file = await readFile(node.absolutePath); const file = await readFile(node.absolutePath);
const iptcData = iptc(file); const iptcData = iptc(file);
const exifData = await read(node.absolutePath); const exifData = await read(node.absolutePath);
@ -123,8 +124,13 @@ exports.onCreateNode = async function ({ node, actions }) {
createNodeField({ createNodeField({
node, node,
name: 'imageMeta', name: "imageMeta",
value: transformMetaToNodeData(exifData, iptcData, vibrantData, node.absolutePath), value: transformMetaToNodeData(
exifData,
iptcData,
vibrantData,
node.absolutePath
),
}); });
} }
}; };
@ -135,45 +141,46 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
const { createPage } = actions; const { createPage } = actions;
// get all images // get all images
const galleryImages = await graphql(` const galleryImages = await graphql(`
{ {
allFile(filter: { allFile(filter: { sourceInstanceName: { eq: "gallery" } }) {
sourceInstanceName: { eq: "gallery" }} edges {
) { node {
edges { relativePath
node { base
relativePath, fields {
base, imageMeta {
fields { dateTaken
imageMeta {
dateTaken
}
} }
} }
} }
} }
} }
`); }
`);
// Handle errors // Handle errors
if (galleryImages.errors) { if (galleryImages.errors) {
reporter.panicOnBuild('Error while running GraphQL query.'); reporter.panicOnBuild("Error while running GraphQL query.");
return; return;
} }
// Create pages for each markdown file. // Create pages for each markdown file.
const galleryImageTemplate = path.resolve('src/components/GalleryImage/GalleryImage.js'); const galleryImageTemplate = path.resolve(
"src/components/GalleryImage/GalleryImage.js"
);
// const diffDate = (a, b) => // const diffDate = (a, b) =>
// new Date(R.path(['node', 'childImageSharp', 'fields', 'imageMeta', 'dateTaken'], a)).getTime() - new Date(R.path(['node', 'childImageSharp', 'fields', 'imageMeta', 'dateTaken'],b)).getTime(); // new Date(R.path(['node', 'childImageSharp', 'fields', 'imageMeta', 'dateTaken'], a)).getTime() - new Date(R.path(['node', 'childImageSharp', 'fields', 'imageMeta', 'dateTaken'],b)).getTime();
const edges = R.sort(R.descend((edge) => const edges = R.sort(
new Date(R.path(['node', 'fields', 'imageMeta', 'dateTaken'], edge))), R.descend(
galleryImages.data.allFile.edges); (edge) =>
new Date(R.path(["node", "fields", "imageMeta", "dateTaken"], edge))
),
galleryImages.data.allFile.edges
);
edges.forEach(({ node }, index) => { edges.forEach(({ node }, index) => {
const nextImage = index === edges.length - 1 const nextImage =
? null index === edges.length - 1 ? null : edges[index + 1].node.base;
: edges[index + 1].node.base; const prevImage = index === 0 ? null : edges[index - 1].node.base;
const prevImage = index === 0
? null
: edges[index - 1].node.base;
const page = { const page = {
path: `photogallery/${node.base}`, path: `photogallery/${node.base}`,
component: galleryImageTemplate, component: galleryImageTemplate,

View File

@ -1 +1 @@
import './src/styles/global.css'; import "./src/styles/global.css";

View File

@ -1,7 +1,7 @@
module.exports = { module.exports = {
plugins: { plugins: {
tailwindcss: {}, tailwindcss: {},
'postcss-nested': {}, "postcss-nested": {},
autoprefixer: {}, autoprefixer: {},
}, },
}; };

View File

@ -1,4 +1,4 @@
import preval from 'babel-plugin-preval/macro'; import preval from "babel-plugin-preval/macro";
const themeBreakpoints = preval` const themeBreakpoints = preval`
const R = require('ramda') const R = require('ramda')
const resolveConfig = require('tailwindcss/resolveConfig'); const resolveConfig = require('tailwindcss/resolveConfig');

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from "react";
import { graphql, navigate, Link } from 'gatsby'; import { graphql, navigate, Link } from "gatsby";
import { import {
getAspectRatio, getAspectRatio,
getMeta, getMeta,
@ -8,19 +8,21 @@ import {
getVibrant, getVibrant,
getVibrantToHelmetSafeBodyStyle, getVibrantToHelmetSafeBodyStyle,
hasName, hasName,
} from '../../utils'; } from "../../utils";
import { GatsbyImage, getImage } from 'gatsby-plugin-image'; import { GatsbyImage, getImage } from "gatsby-plugin-image";
import { Helmet } from 'react-helmet'; import { Helmet } from "react-helmet";
import classnames from 'classnames'; import classnames from "classnames";
import posthog from 'posthog-js'; import posthog from "posthog-js";
import MetadataItem from './MetadataItem'; import MetadataItem from "./MetadataItem";
const logKeyShortcut = (keyCode) => { const logKeyShortcut = (keyCode) => {
try { try {
// eslint-disable-next-line // eslint-disable-next-line
posthog.capture('[key shortcut]', { keyCode }); posthog.capture("[key shortcut]", { keyCode });
window.plausible('KeyShortcut', {props: { keyCode }}); window.plausible("KeyShortcut", { props: { keyCode } });
} catch (e) {/* do nothing */} } catch (e) {
/* do nothing */
}
}; };
const GalleryImage = ({ data, pageContext }) => { const GalleryImage = ({ data, pageContext }) => {
@ -29,32 +31,31 @@ const GalleryImage = ({ data, pageContext }) => {
React.useEffect(() => { React.useEffect(() => {
const keyListener = (e) => { const keyListener = (e) => {
switch (e.code) { switch (e.code) {
case 'ArrowRight': { case "ArrowRight": {
logKeyShortcut(e.code); logKeyShortcut(e.code);
if (pageContext.nextImage) { if (pageContext.nextImage) {
navigate(`/photogallery/${pageContext.nextImage}/`); navigate(`/photogallery/${pageContext.nextImage}/`);
}
return;
} }
return; case "ArrowLeft": {
} logKeyShortcut(e.code);
case 'ArrowLeft': { if (pageContext.prevImage) {
logKeyShortcut(e.code); navigate(`/photogallery/${pageContext.prevImage}/`);
if (pageContext.prevImage) { }
navigate(`/photogallery/${pageContext.prevImage}/`); return;
}
case "Escape":
case "KeyG": {
logKeyShortcut(e.code);
navigate("/photogallery/");
} }
return;
}
case 'Escape':
case 'KeyG': {
logKeyShortcut(e.code);
navigate('/photogallery/');
}
} }
}; };
document.addEventListener('keydown', keyListener); document.addEventListener("keydown", keyListener);
return () => { return () => {
document.removeEventListener('keydown', keyListener); document.removeEventListener("keydown", keyListener);
}; };
}, [pageContext]); }, [pageContext]);
@ -62,128 +63,193 @@ const GalleryImage = ({ data, pageContext }) => {
const meta = getMeta(image); const meta = getMeta(image);
let locationString; let locationString;
if (meta.iptc.city || meta.iptc.province_or_state) { if (meta.iptc.city || meta.iptc.province_or_state) {
const location = [meta.iptc.city, meta.iptc.province_or_state].filter(Boolean); const location = [meta.iptc.city, meta.iptc.province_or_state].filter(
locationString = location.join(', '); Boolean
);
locationString = location.join(", ");
} }
const vibrant = getVibrant(image, true); const vibrant = getVibrant(image, true);
const orientationClasses = ar > 1 ? 'flex-col mx-auto' : 'portrait:mx-auto landscape:mx-5 landscape:flex-row-reverse portrait:flex-col'; const orientationClasses =
const shutterSpeed = React.useMemo(() => getShutterFractionFromExposureTime(meta.exif.ExposureTime || 0), [meta]); ar > 1
? "flex-col mx-auto"
: "portrait:mx-auto landscape:mx-5 landscape:flex-row-reverse portrait:flex-col";
const shutterSpeed = React.useMemo(
() => getShutterFractionFromExposureTime(meta.exif.ExposureTime || 0),
[meta]
);
const dateTaken = React.useMemo(() => new Date(meta.dateTaken), [meta]); const dateTaken = React.useMemo(() => new Date(meta.dateTaken), [meta]);
return (<> return (
<Helmet> <>
<title>{name} - Gallery | Chuck Dries</title> <Helmet>
<body <title>{name} - Gallery | Chuck Dries</title>
className="text-vibrant-light bg-vibrant-dark" <body
style={getVibrantToHelmetSafeBodyStyle(vibrant)} className="text-vibrant-light bg-vibrant-dark"
/> style={getVibrantToHelmetSafeBodyStyle(vibrant)}
</Helmet> />
<div className="min-h-screen flex flex-col justify-between"> </Helmet>
<nav className="mt-1 ml-1 text-lg mb-4"> <div className="min-h-screen flex flex-col justify-between">
<button <nav className="mt-1 ml-1 text-lg mb-4">
className="hover:underline text-vibrant-light hover:text-muted-light arrow-left-before mr-1" <button
onClick={() => navigate(-1)} className="hover:underline text-vibrant-light hover:text-muted-light arrow-left-before mr-1"
type="button" onClick={() => navigate(-1)}
>back</button> type="button"
<Link >
className="hover:underline text-vibrant-light hover:text-muted-light mx-1" back
to="/" </button>
>home</Link> <Link
<Link className="hover:underline text-vibrant-light hover:text-muted-light mx-1"
className="hover:underline text-vibrant-light hover:text-muted-light mx-1" to="/"
to="/photogallery/" >
>gallery <span className="bg-gray-300 text-black">esc</span></Link> home
{pageContext.prevImage && <Link </Link>
className="hover:underline text-vibrant-light hover:text-muted-light mx-1" <Link
to={`/photogallery/${pageContext.prevImage}/`} className="hover:underline text-vibrant-light hover:text-muted-light mx-1"
>previous <span className="bg-gray-300 text-black">&#11104;</span></Link>} to="/photogallery/"
{pageContext.nextImage && <Link >
className="hover:underline text-vibrant-light hover:text-muted-light mx-1" gallery <span className="bg-gray-300 text-black">esc</span>
to={`/photogallery/${pageContext.nextImage}/`} </Link>
>next <span className="bg-gray-300 text-black">&#11106;</span></Link>} {pageContext.prevImage && (
</nav> <Link
<div className={classnames('flex', orientationClasses)}> className="hover:underline text-vibrant-light hover:text-muted-light mx-1"
<div className="flex-grow-0"> to={`/photogallery/${pageContext.prevImage}/`}
<GatsbyImage >
alt={name} previous <span className="bg-gray-300 text-black">&#11104;</span>
className="" </Link>
image={getImage(image)} )}
key={image.base} {pageContext.nextImage && (
loading="eager" <Link
objectFit="contain" className="hover:underline text-vibrant-light hover:text-muted-light mx-1"
style={{ to={`/photogallery/${pageContext.nextImage}/`}
maxWidth: `calc(max(90vh, 500px) * ${ar})`, >
maxHeight: '90vh', next <span className="bg-gray-300 text-black">&#11106;</span>
// minHeight: '500px', </Link>
}} /> )}
</div> </nav>
<div className={classnames( <div className={classnames("flex", orientationClasses)}>
'flex-shrink-0 mx-2 flex flex-row portrait:items-end', ar <= 1 <div className="flex-grow-0">
? 'pt-5 flex-col flex-auto text-right' <GatsbyImage
: 'portrait:pt-5 portrait:flex-col portrait:text-right' alt={name}
)}> className=""
<div className="flex-auto mr-2"> image={getImage(image)}
<p className="text-muted-light font-mono text-sm m-0 mt-1">{image.base}</p> key={image.base}
{hasName(image) && <h1 className="text-4xl mt-0 font-serif">{name}</h1>} loading="eager"
<p className="landscape:mr-2">{meta.iptc.caption}</p> objectFit="contain"
style={{
maxWidth: `calc(max(90vh, 500px) * ${ar})`,
maxHeight: "90vh",
// minHeight: '500px',
}}
/>
</div>
<div
className={classnames(
"flex-shrink-0 mx-2 flex flex-row portrait:items-end",
ar <= 1
? "pt-5 flex-col flex-auto text-right"
: "portrait:pt-5 portrait:flex-col portrait:text-right"
)}
>
<div className="flex-auto mr-2">
<p className="text-muted-light font-mono text-sm m-0 mt-1">
{image.base}
</p>
{hasName(image) && (
<h1 className="text-4xl mt-0 font-serif">{name}</h1>
)}
<p className="landscape:mr-2">{meta.iptc.caption}</p>
</div>
{
<div
className="portrait:border-t-2 border-muted-light portrait:mt-2 mr-2 portrait:mb-1"
style={{ width: 30 }}
></div>
}
<MetadataItem
aspectRatio={ar}
data={dateTaken.toLocaleDateString()}
icon="calendar-sharp"
title="date taken"
/>
<MetadataItem
aspectRatio={ar}
data={locationString}
icon="location-sharp"
title="location"
/>
<MetadataItem
aspectRatio={ar}
data={shutterSpeed}
icon="stopwatch-sharp"
title="shutter speed"
/>
<MetadataItem
aspectRatio={ar}
data={`f/${meta.exif.FNumber}`}
icon="aperture-sharp"
title="aperture"
/>
<MetadataItem
aspectRatio={ar}
data={meta.exif.ISO}
icon="film-outline"
title="ISO"
/>
</div> </div>
{<div className="portrait:border-t-2 border-muted-light portrait:mt-2 mr-2 portrait:mb-1" style={{width: 30}}></div>}
<MetadataItem aspectRatio={ar} data={dateTaken.toLocaleDateString()} icon="calendar-sharp" title="date taken"/>
<MetadataItem aspectRatio={ar} data={locationString} icon="location-sharp" title="location"/>
<MetadataItem aspectRatio={ar} data={shutterSpeed} icon="stopwatch-sharp" title="shutter speed" />
<MetadataItem aspectRatio={ar} data={`f/${meta.exif.FNumber}`} icon="aperture-sharp" title="aperture" />
<MetadataItem aspectRatio={ar} data={meta.exif.ISO} icon="film-outline" title="ISO" />
</div> </div>
<div></div>
</div> </div>
<div></div> </>
</div> );
</>);
}; };
export const query = graphql` export const query = graphql`
query GalleryImage($imageFilename: String) { query GalleryImage($imageFilename: String) {
allFile(filter: {sourceInstanceName: {eq: "gallery"}, base: {eq: $imageFilename}}) { allFile(
edges { filter: {
node { sourceInstanceName: { eq: "gallery" }
base base: { eq: $imageFilename }
childImageSharp{ }
fluid { ) {
aspectRatio edges {
node {
base
childImageSharp {
fluid {
aspectRatio
}
gatsbyImageData(
layout: CONSTRAINED
# placeholder: BLURRED
placeholder: DOMINANT_COLOR
# placeholder: TRACED_SVG
height: 2160
)
} }
gatsbyImageData( fields {
layout: CONSTRAINED imageMeta {
# placeholder: BLURRED dateTaken
placeholder: DOMINANT_COLOR iptc {
# placeholder: TRACED_SVG caption
height: 2160 object_name
) keywords
} city
fields { province_or_state
imageMeta { }
dateTaken exif {
iptc { FNumber
caption ExposureTime
object_name ISO
keywords }
city vibrant {
province_or_state ...VibrantColors
} }
exif {
FNumber
ExposureTime
ISO
}
vibrant {
...VibrantColors
} }
} }
} }
} }
} }
} }
}
`; `;
export default GalleryImage; export default GalleryImage;

View File

@ -1,21 +1,20 @@
import classNames from 'classnames'; import classNames from "classnames";
import React from 'react'; import React from "react";
const MetadataItem = ({ const MetadataItem = ({ aspectRatio, icon, data, title }) =>
aspectRatio, data ? (
icon, <div
data, className={classNames(
title, "flex items-baseline ml-2 text-lg",
}) => data ? ( aspectRatio <= 1 ? "flex-row-reverse" : "portrait:flex-row-reverse"
<div className={classNames('flex items-baseline ml-2 text-lg', )}
aspectRatio <= 1 ? 'flex-row-reverse' : 'portrait:flex-row-reverse')} title={title}
title={title} >
> <span className="icon-offset mr-1">
<span className="icon-offset mr-1"> <ion-icon name={icon}></ion-icon>
<ion-icon name={icon}></ion-icon> </span>
</span> <span className="mr-1">{data}</span>
<span className="mr-1">{data}</span> </div>
</div> ) : null;
) : null;
export default MetadataItem; export default MetadataItem;

View File

@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from "react";
import classnames from 'classnames'; import classnames from "classnames";
export const HeroA = ({ export const HeroA = ({
href, href,
@ -9,8 +9,14 @@ export const HeroA = ({
...linkProps ...linkProps
}) => ( }) => (
<a <a
className={classnames('mx-2 underline', isClient && 'text-muted-light hover:text-vibrant-light', className)} className={classnames(
"mx-2 underline",
isClient && "text-muted-light hover:text-vibrant-light",
className
)}
href={href} href={href}
{...linkProps} {...linkProps}
>{children}</a> >
{children}
</a>
); );

View File

@ -1,33 +1,41 @@
import * as React from 'react'; import * as React from "react";
import { Link } from 'gatsby'; import { Link } from "gatsby";
import { GatsbyImage, getImage } from 'gatsby-plugin-image'; import { GatsbyImage, getImage } from "gatsby-plugin-image";
import * as R from 'ramda'; import * as R from "ramda";
import { getAspectRatio, getName } from '../utils'; import { getAspectRatio, getName } from "../utils";
import useBreakpoint from 'use-breakpoint'; import useBreakpoint from "use-breakpoint";
import themeBreakpoints from '../breakpoints'; import themeBreakpoints from "../breakpoints";
const MasonryGallery = ({ images, itemsPerRow: itemsPerRowByBreakpoint }) => { const MasonryGallery = ({ images, itemsPerRow: itemsPerRowByBreakpoint }) => {
const breakpoints = React.useMemo(() => const breakpoints = React.useMemo(
R.pick(R.keys(itemsPerRowByBreakpoint), themeBreakpoints) () => R.pick(R.keys(itemsPerRowByBreakpoint), themeBreakpoints),
, [itemsPerRowByBreakpoint]); [itemsPerRowByBreakpoint]
);
const { breakpoint } = useBreakpoint(breakpoints, 'sm'); const { breakpoint } = useBreakpoint(breakpoints, "sm");
const aspectRatios = React.useMemo(() => R.map(getAspectRatio, images), [images]); const aspectRatios = React.useMemo(
const rowAspectRatioSumsByBreakpoint = React.useMemo(() => R.map(R.pipe( () => R.map(getAspectRatio, images),
R.splitEvery(R.__, aspectRatios), [images]
R.map(R.sum) );
))(itemsPerRowByBreakpoint), [aspectRatios, itemsPerRowByBreakpoint]); const rowAspectRatioSumsByBreakpoint = React.useMemo(
() =>
R.map(R.pipe(R.splitEvery(R.__, aspectRatios), R.map(R.sum)))(
itemsPerRowByBreakpoint
),
[aspectRatios, itemsPerRowByBreakpoint]
);
const itemsPerRow = itemsPerRowByBreakpoint[breakpoint]; const itemsPerRow = itemsPerRowByBreakpoint[breakpoint];
const rowAspectRatioSumsForCurrentBP = rowAspectRatioSumsByBreakpoint[breakpoint]; const rowAspectRatioSumsForCurrentBP =
rowAspectRatioSumsByBreakpoint[breakpoint];
return ( return (
<div <div
className="w-full" className="w-full"
style={{ style={{
position: 'relative', position: "relative",
}} }}
// style={{ maxWidth: minWidth }} // style={{ maxWidth: minWidth }}
> >
@ -36,22 +44,24 @@ const MasonryGallery = ({ images, itemsPerRow: itemsPerRowByBreakpoint }) => {
const rowAspectRatioSum = rowAspectRatioSumsForCurrentBP[rowIndex]; const rowAspectRatioSum = rowAspectRatioSumsForCurrentBP[rowIndex];
// const width = ((getAspectRatio(image) / rowAspectRatioSum) * 100).toFixed(10); // const width = ((getAspectRatio(image) / rowAspectRatioSum) * 100).toFixed(10);
const ar = getAspectRatio(image); const ar = getAspectRatio(image);
const widthNumber = rowAspectRatioSum === ar const widthNumber =
// image is only one in row rowAspectRatioSum === ar
? 100 / itemsPerRow ? // image is only one in row
// image is one of several in row 100 / itemsPerRow
: ((ar / rowAspectRatioSum) * 100).toFixed(5); : // image is one of several in row
((ar / rowAspectRatioSum) * 100).toFixed(5);
const width = `${widthNumber}%`; const width = `${widthNumber}%`;
return ( return (
<Link <Link
className="inline-block" className="inline-block"
key={`${image.base}`} key={`${image.base}`}
state={{modal: true}} state={{ modal: true }}
style={{ style={{
width, width,
// marginLeft: '8px', // marginLeft: '8px',
}} to={`/photogallery/${image.base}`} }}
to={`/photogallery/${image.base}`}
> >
<GatsbyImage <GatsbyImage
alt={getName(image)} alt={getName(image)}
@ -63,7 +73,8 @@ const MasonryGallery = ({ images, itemsPerRow: itemsPerRowByBreakpoint }) => {
</Link> </Link>
); );
})} })}
</div>); </div>
);
// return null; // return null;
}; };

View File

@ -1,10 +1,10 @@
import * as React from 'react'; import * as React from "react";
import classnames from 'classnames'; import classnames from "classnames";
import { MDXProvider } from '@mdx-js/react'; import { MDXProvider } from "@mdx-js/react";
import '../../styles/resume.css'; import "../../styles/resume.css";
const MyH1 = props => <h1 style={{ color: 'tomato' }} {...props} />; const MyH1 = (props) => <h1 style={{ color: "tomato" }} {...props} />;
// const MyParagraph = props => ( // const MyParagraph = props => (
// <p style={{ fontSize: '18px', lineHeight: 1.6 }} {...props} /> // <p style={{ fontSize: '18px', lineHeight: 1.6 }} {...props} />
// ); // );
@ -14,12 +14,13 @@ const components = {
// p: MyParagraph, // p: MyParagraph,
}; };
const ResumeLayout = ({ pageContext, children }) => { const ResumeLayout = ({ pageContext, children }) => {
console.log('pc', pageContext); console.log("pc", pageContext);
return ( return (
<MDXProvider components={components}> <MDXProvider components={components}>
<div className={classnames('font-serif container mx-auto resume')}>{children}</div> <div className={classnames("font-serif container mx-auto resume")}>
{children}
</div>
</MDXProvider> </MDXProvider>
); );
}; };

View File

@ -1,4 +1,4 @@
import { graphql } from 'gatsby'; import { graphql } from "gatsby";
export const VibrantColorsFragment = graphql` export const VibrantColorsFragment = graphql`
fragment VibrantColors on FileFieldsImageMetaVibrant { fragment VibrantColors on FileFieldsImageMetaVibrant {

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from "react";
import PropTypes from 'prop-types'; import PropTypes from "prop-types";
const env = process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || 'development'; const env =
process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || "development";
export default function HTML(props) { export default function HTML(props) {
return ( return (
@ -9,25 +10,40 @@ export default function HTML(props) {
<head> <head>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
<meta content="ie=edge" httpEquiv="x-ua-compatible" /> <meta content="ie=edge" httpEquiv="x-ua-compatible" />
<meta content="Chuck Dries: Full Stack Software Engineer and Photographer" name="description" /> <meta
content="Chuck Dries: Full Stack Software Engineer and Photographer"
name="description"
/>
<meta <meta
content="width=device-width, initial-scale=1, shrink-to-fit=no" content="width=device-width, initial-scale=1, shrink-to-fit=no"
name="viewport" name="viewport"
/> />
{props.headComponents} {props.headComponents}
{env === 'production' && <script async data-domain="chuckdries.com" defer src="https://analytics.chuckdries.com/js/plausible.js"></script>} {env === "production" && (
<script
async
data-domain="chuckdries.com"
defer
src="https://analytics.chuckdries.com/js/plausible.js"
></script>
)}
{/* eslint-disable-next-line */} {/* eslint-disable-next-line */}
{env === 'production' && <script>{`window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }`}</script>} {env === "production" && (
<script>{`window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }`}</script>
)}
</head> </head>
<body {...props.bodyAttributes}> <body {...props.bodyAttributes}>
{props.preBodyComponents} {props.preBodyComponents}
<div <div
dangerouslySetInnerHTML={{ __html: props.body }} dangerouslySetInnerHTML={{ __html: props.body }}
id="___gatsby" id="___gatsby"
key={'body'} key={"body"}
/> />
{props.postBodyComponents} {props.postBodyComponents}
<script src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js" type="module"></script> <script
src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"
type="module"
></script>
</body> </body>
</html> </html>
); );

View File

@ -1,11 +1,11 @@
import * as React from 'react'; import * as React from "react";
import { Link } from 'gatsby'; import { Link } from "gatsby";
// styles // styles
const pageStyles = { const pageStyles = {
color: '#232129', color: "#232129",
padding: '96px', padding: "96px",
fontFamily: '-apple-system, Roboto, sans-serif, serif', fontFamily: "-apple-system, Roboto, sans-serif, serif",
}; };
const headingStyles = { const headingStyles = {
marginTop: 0, marginTop: 0,
@ -17,10 +17,10 @@ const paragraphStyles = {
marginBottom: 48, marginBottom: 48,
}; };
const codeStyles = { const codeStyles = {
color: '#8A6534', color: "#8A6534",
padding: 4, padding: 4,
backgroundColor: '#FFF4DB', backgroundColor: "#FFF4DB",
fontSize: '1.25rem', fontSize: "1.25rem",
borderRadius: 4, borderRadius: 4,
}; };
@ -31,13 +31,13 @@ const NotFoundPage = () => {
<title>Not found</title> <title>Not found</title>
<h1 style={headingStyles}>Page not found</h1> <h1 style={headingStyles}>Page not found</h1>
<p style={paragraphStyles}> <p style={paragraphStyles}>
Sorry{' '} Sorry{" "}
<span aria-label="Pensive emoji" role="img"> <span aria-label="Pensive emoji" role="img">
😔 😔
</span>{' '} </span>{" "}
we couldnt find what you were looking for. we couldnt find what you were looking for.
<br /> <br />
{process.env.NODE_ENV === 'development' ? ( {process.env.NODE_ENV === "development" ? (
<> <>
<br /> <br />
Try creating a page in <code style={codeStyles}>src/pages/</code>. Try creating a page in <code style={codeStyles}>src/pages/</code>.

View File

@ -2,8 +2,8 @@
title: Charles Dries Resume title: Charles Dries Resume
--- ---
import ResumeLayout from '../components/resume/ResumeLayout' import ResumeLayout from "../components/resume/ResumeLayout";
export default ResumeLayout export default ResumeLayout;
# Hello, World! # Hello, World!

View File

@ -1,47 +1,69 @@
import * as React from 'react'; import * as React from "react";
import { Link, graphql } from 'gatsby'; import { Link, graphql } from "gatsby";
import { GatsbyImage, getImage } from 'gatsby-plugin-image'; import { GatsbyImage, getImage } from "gatsby-plugin-image";
import { Helmet } from 'react-helmet'; import { Helmet } from "react-helmet";
import { take } from 'ramda'; import { take } from "ramda";
import classnames from 'classnames'; import classnames from "classnames";
import posthog from 'posthog-js'; import posthog from "posthog-js";
import { getVibrantToHelmetSafeBodyStyle, getVibrant, getAspectRatio } from '../utils'; import {
import { HeroA } from '../components/Index/HeroLink'; getVibrantToHelmetSafeBodyStyle,
getVibrant,
getAspectRatio,
} from "../utils";
import { HeroA } from "../components/Index/HeroLink";
const env = process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || 'development'; const env =
process.env.GATSBY_ACTIVE_ENV || process.env.NODE_ENV || "development";
const getDifferentRand = (range, lastNs, iterations = 0) => { const getDifferentRand = (range, lastNs, iterations = 0) => {
const n = Math.floor(Math.random() * range); const n = Math.floor(Math.random() * range);
if (lastNs.findIndex(x => x === n) > -1 && iterations < 5) { if (lastNs.findIndex((x) => x === n) > -1 && iterations < 5) {
console.log('got dupe, trying again', n); console.log("got dupe, trying again", n);
return getDifferentRand(range, lastNs, iterations + 1); return getDifferentRand(range, lastNs, iterations + 1);
} }
return n; return n;
}; };
const IndexPage = ({ data: { allFile: { edges } } }) => { const IndexPage = ({
data: {
allFile: { edges },
},
}) => {
const [isClient, setIsClient] = React.useState(false); const [isClient, setIsClient] = React.useState(false);
const [imageIndex, setImageIndex] = React.useState(0); const [imageIndex, setImageIndex] = React.useState(0);
const images = React.useMemo(() => edges.map((edge) => edge.node), [edges]); const images = React.useMemo(() => edges.map((edge) => edge.node), [edges]);
const image = React.useMemo(() => { const image = React.useMemo(() => {
console.log('ii', imageIndex); console.log("ii", imageIndex);
return images[imageIndex]; return images[imageIndex];
}, [images, imageIndex]); }, [images, imageIndex]);
const shuffleImage = React.useCallback((currentImage) => { const shuffleImage = React.useCallback(
const lastThreeImages = JSON.parse(localStorage.getItem('lastHeros')) || []; (currentImage) => {
if (env === 'production') { const lastThreeImages =
try { JSON.parse(localStorage.getItem("lastHeros")) || [];
// eslint-disable-next-line if (env === "production") {
posthog.capture('[shuffle image]', { currentImage: currentImage?.base }); try {
window.plausible('Shuffle', {props: { currentImage: currentImage?.base }}); // eslint-disable-next-line
} catch (e) {/* do nothing */} posthog.capture("[shuffle image]", {
} currentImage: currentImage?.base,
const index = getDifferentRand(images.length, lastThreeImages); });
localStorage.setItem('lastHeros', JSON.stringify(take(3, [index, ...lastThreeImages]))); window.plausible("Shuffle", {
setImageIndex(index); props: { currentImage: currentImage?.base },
}, [images.length]); });
} catch (e) {
/* do nothing */
}
}
const index = getDifferentRand(images.length, lastThreeImages);
localStorage.setItem(
"lastHeros",
JSON.stringify(take(3, [index, ...lastThreeImages]))
);
setImageIndex(index);
},
[images.length]
);
// pick random image on page hydration // pick random image on page hydration
React.useEffect(() => { React.useEffect(() => {
@ -54,165 +76,257 @@ const IndexPage = ({ data: { allFile: { edges } } }) => {
React.useEffect(() => { React.useEffect(() => {
const keyListener = (e) => { const keyListener = (e) => {
switch (e.code) { switch (e.code) {
case 'ArrowRight': { case "ArrowRight": {
if (imageIndex === images.length - 1) { if (imageIndex === images.length - 1) {
setImageIndex(0); setImageIndex(0);
return;
}
setImageIndex(imageIndex + 1);
return; return;
} }
setImageIndex(imageIndex + 1);
return;
}
case 'ArrowLeft': { case "ArrowLeft": {
if (imageIndex === 0) { if (imageIndex === 0) {
setImageIndex(images.length - 1); setImageIndex(images.length - 1);
return;
}
setImageIndex(imageIndex - 1);
return; return;
} }
setImageIndex(imageIndex - 1);
return;
}
} }
}; };
document.addEventListener('keydown', keyListener); document.addEventListener("keydown", keyListener);
return () => { return () => {
document.removeEventListener('keydown', keyListener); document.removeEventListener("keydown", keyListener);
}; };
}, [imageIndex, images.length]); }, [imageIndex, images.length]);
const vibrant = getVibrant(image); const vibrant = getVibrant(image);
const ar = getAspectRatio(image); const ar = getAspectRatio(image);
return (<> return (
<Helmet> <>
<title>Chuck Dries</title> <Helmet>
<body <title>Chuck Dries</title>
className={classnames(isClient ? 'bg-vibrant-dark' : '')} <body
style={getVibrantToHelmetSafeBodyStyle(vibrant)} className={classnames(isClient ? "bg-vibrant-dark" : "")}
/> style={getVibrantToHelmetSafeBodyStyle(vibrant)}
</Helmet> />
{/* WIP: ipad portrait hits md breakpoint, looks bad */} </Helmet>
<main {/* WIP: ipad portrait hits md breakpoint, looks bad */}
className={classnames('font-serif hero', ar > 1 || !isClient <main
? 'landscape:grid portrait:flex portrait:flex-col' : 'portrait:grid landscape:flex landscape:flex-row-reverse')} className={classnames(
> "font-serif hero",
{isClient ? ar > 1 || !isClient
<GatsbyImage ? "landscape:grid portrait:flex portrait:flex-col"
alt="" : "portrait:grid landscape:flex landscape:flex-row-reverse"
)}
>
{isClient ? (
<GatsbyImage
alt=""
className={classnames(
ar > 1 || !isClient
? "landscape:h-screen portrait:h-two-thirds-vw"
: "h-screen portrait:w-full landscape:w-1/2"
)}
image={getImage(image)}
loading="eager"
style={{
gridArea: "1/1",
}}
/>
) : (
// 67vw = 1/1.49253731 = 1/aspect ratio of my camera lol
<div
className="landscape:h-screen portrait:h-two-thirds-vw w-full"
style={{ gridArea: "1/1" }}
></div>
)}
<div
className={classnames( className={classnames(
ar > 1 || !isClient ? 'landscape:h-screen portrait:h-two-thirds-vw' : 'h-screen portrait:w-full landscape:w-1/2', "relative grid",
ar <= 1
? "place-items-end landscape:place-items-center"
: "place-items-end"
)} )}
image={getImage(image)} style={{ gridArea: "1/1" }}
loading="eager" >
style={{ <div className="">
gridArea: '1/1', <div className="flex mx-6 justify-end">
}} /> <div className="flex my-2 items-center flex-col">
// 67vw = 1/1.49253731 = 1/aspect ratio of my camera lol <Link
: <div className="landscape:h-screen portrait:h-two-thirds-vw w-full" style={{gridArea: '1/1' }}></div> } className={classnames(
<div className={classnames('relative grid', ar <= 1 ? 'place-items-end landscape:place-items-center' : 'place-items-end')} style={{gridArea: '1/1'}}> "hover:underline inline-block px-1 my-1 mr-2 text-md rounded-md border-2",
<div className=""> isClient &&
<div className="flex mx-6 justify-end"> "text-muted-dark bg-muted-light hover:border-muted border-muted-dark"
<div className="flex my-2 items-center flex-col"> )}
// style={{top: '5px'}}
id="image-link"
title="view image details"
to={`/photogallery/${image.base}/`}
>
<span className="icon-offset">
<ion-icon name="image"></ion-icon>
</span>
</Link>
<button
className={classnames(
"hover:underline inline-block px-1 my-1 mr-2 text-md rounded-md border-2",
isClient &&
"text-muted-dark bg-muted-light hover:border-muted border-muted-dark"
)}
id="shuffle-button"
onClick={() => {
shuffleImage(image);
}}
title="shuffle image"
type="button"
>
<span className="icon-offset">
<ion-icon name="shuffle"></ion-icon>
</span>
</button>
</div>
<Link <Link
className={classnames( className={classnames(
'hover:underline inline-block px-1 my-1 mr-2 text-md rounded-md border-2', "hover:underline p-3 px-5 py-4 my-3 text-md sm:text-lg rounded-md border-2 arrow-right-after font-bold font-serif",
isClient && 'text-muted-dark bg-muted-light hover:border-muted border-muted-dark')} isClient &&
// style={{top: '5px'}} "text-muted-dark bg-muted-light hover:border-muted border-muted-dark"
id="image-link" )}
title="view image details" id="photogallery-link"
to={`/photogallery/${image.base}/`} to="/photogallery/"
> >
<span className="icon-offset"><ion-icon name="image"></ion-icon></span> Photography Gallery
</Link> </Link>
<button
className={classnames(
'hover:underline inline-block px-1 my-1 mr-2 text-md rounded-md border-2',
isClient && 'text-muted-dark bg-muted-light hover:border-muted border-muted-dark')}
id="shuffle-button"
onClick={() => {
shuffleImage(image);
}}
title="shuffle image"
type="button"
>
<span className="icon-offset"><ion-icon name="shuffle"></ion-icon></span>
</button>
</div> </div>
<Link <section
className={classnames( className={classnames(
'hover:underline p-3 px-5 py-4 my-3 text-md sm:text-lg rounded-md border-2 arrow-right-after font-bold font-serif', ar > 1 && "landscape:shadow-lg",
isClient && 'text-muted-dark bg-muted-light hover:border-muted border-muted-dark')} "md:px-6 px-4 md:py-5 py-3 rounded-l-md mb-4",
id="photogallery-link" isClient &&
to="/photogallery/" "bg-vibrant-dark bg-opacity-60 backdrop-filter backdrop-blur-xl"
)}
> >
Photography Gallery <div
</Link> className={classnames(
</div> "mx-auto filter drop-shadow items-end",
<section ar > 1 || !isClient ? "landscape:flex" : "portrait:flex"
className={classnames( )}
ar > 1 && 'landscape:shadow-lg', >
'md:px-6 px-4 md:py-5 py-3 rounded-l-md mb-4', isClient && <div className="mr-5 flex-auto">
'bg-vibrant-dark bg-opacity-60 backdrop-filter backdrop-blur-xl' <h1
)} className={classnames(
> "font-black text-4xl sm:text-5xl md:text-6xl",
<div isClient && "text-vibrant-light"
className={classnames('mx-auto filter drop-shadow items-end', ar > 1 || !isClient ? 'landscape:flex' : 'portrait:flex')} )}
> >
<div className="mr-5 flex-auto"> Chuck Dries
<h1 className={classnames('font-black text-4xl sm:text-5xl md:text-6xl', isClient && 'text-vibrant-light')}>Chuck Dries</h1> </h1>
<h2 className={classnames('text-xl md:text-2xl', isClient && 'text-vibrant')}>Full Stack Software Engineer &amp; Hobbyist Photographer</h2> <h2
className={classnames(
"text-xl md:text-2xl",
isClient && "text-vibrant"
)}
>
Full Stack Software Engineer &amp; Hobbyist Photographer
</h2>
</div>
{/* {<div className="border-t-2 border-muted-light mt-2 mr-2 mb-1" style={{width: 30}}></div>} */}
<ul
className={
("md:mr-4", classnames(isClient && "text-muted-light"))
}
>
<li>Software Engineer, Axosoft</li>
<li>
<HeroA
className="ml-0"
href="mailto:chuck@chuckdries.com"
isClient={isClient}
>
chuck@chuckdries.com
</HeroA>
/<span className="ml-2">602.618.0414</span>
</li>
<li>
<HeroA
className="ml-0"
href="http://github.com/chuckdries"
isClient={isClient}
>
Github
</HeroA>
/
<HeroA
href="https://www.linkedin.com/in/chuckdries/"
isClient={isClient}
>
LinkedIn
</HeroA>
/
<HeroA
href="https://devpost.com/chuckdries"
isClient={isClient}
>
Devpost
</HeroA>
/
<HeroA
href="/CharlesDriesResumeCurrent.pdf"
isClient={isClient}
>
Resume [pdf]
</HeroA>
/
<HeroA
href="https://medium.com/@chuckdries"
isClient={isClient}
>
Medium (blog)
</HeroA>
</li>
</ul>
</div> </div>
{/* {<div className="border-t-2 border-muted-light mt-2 mr-2 mb-1" style={{width: 30}}></div>} */} </section>
</div>
<ul className={'md:mr-4', classnames(isClient && 'text-muted-light')}>
<li>Software Engineer, Axosoft</li>
<li><HeroA className="ml-0" href="mailto:chuck@chuckdries.com" isClient={isClient}>chuck@chuckdries.com</HeroA>/<span className="ml-2">602.618.0414</span></li>
<li>
<HeroA className="ml-0" href="http://github.com/chuckdries" isClient={isClient}>Github</HeroA>/
<HeroA href="https://www.linkedin.com/in/chuckdries/" isClient={isClient}>LinkedIn</HeroA>/
<HeroA href="https://devpost.com/chuckdries" isClient={isClient}>Devpost</HeroA>/
<HeroA href="/CharlesDriesResumeCurrent.pdf" isClient={isClient}>Resume [pdf]</HeroA>/
<HeroA href="https://medium.com/@chuckdries" isClient={isClient}>Medium (blog)</HeroA>
</li>
</ul>
</div>
</section>
</div> </div>
</div> </main>
</>
</main> );
</>);
}; };
export const query = graphql` export const query = graphql`
{ {
allFile( allFile(
filter: { sourceInstanceName: {eq: "gallery"} } filter: { sourceInstanceName: { eq: "gallery" } }
sort: {order: DESC, fields: fields___imageMeta___dateTaken} sort: { order: DESC, fields: fields___imageMeta___dateTaken }
) { ) {
edges { edges {
node { node {
relativePath relativePath
base base
childImageSharp { childImageSharp {
fluid { fluid {
aspectRatio aspectRatio
}
gatsbyImageData(
layout: FULL_WIDTH
placeholder: NONE
breakpoints: [750, 1080, 1366, 1920, 2560]
)
} }
gatsbyImageData( fields {
layout: FULL_WIDTH imageMeta {
placeholder: NONE vibrant {
breakpoints: [750, 1080, 1366, 1920, 2560] ...VibrantColors
) }
}
fields {
imageMeta {
vibrant {
...VibrantColors
} }
} }
} }
} }
} }
} }
}
`; `;
export default IndexPage; export default IndexPage;

View File

@ -1,87 +1,95 @@
import * as React from 'react'; import * as React from "react";
import { graphql, Link } from 'gatsby'; import { graphql, Link } from "gatsby";
import { navigate } from 'gatsby'; import { navigate } from "gatsby";
import { Helmet } from 'react-helmet'; import { Helmet } from "react-helmet";
import MasonryGallery from '../components/MasonryGallery'; import MasonryGallery from "../components/MasonryGallery";
// TODO: caption and title more images // TODO: caption and title more images
// TODO: more images // TODO: more images
const GalleryPage = ({ data }) => { const GalleryPage = ({ data }) => {
const images = React.useMemo(() => const images = React.useMemo(
data.allFile.edges () => data.allFile.edges.map((edge) => edge.node, [data]),
.map(edge => edge.node, [data]) [data]
, [data]); );
return (<> return (
<Helmet> <>
<title>Photo Gallery | Chuck Dries</title> <Helmet>
<body className="bg-black text-white" /> <title>Photo Gallery | Chuck Dries</title>
</Helmet> <body className="bg-black text-white" />
<nav className="mt-1 ml-1 text-lg mb-4"> </Helmet>
<button <nav className="mt-1 ml-1 text-lg mb-4">
className="hover:underline text-vibrant-light hover:text-muted-light arrow-left-before mr-1" <button
onClick={() => navigate(-1)} className="hover:underline text-vibrant-light hover:text-muted-light arrow-left-before mr-1"
type="button" onClick={() => navigate(-1)}
>back</button> type="button"
<Link >
className="hover:underline text-vibrant-light hover:text-muted-light mx-1" back
to="/" </button>
>home</Link> <Link
<Link className="hover:underline text-vibrant-light hover:text-muted-light mx-1"
className="hover:underline text-vibrant-light hover:text-muted-light mx-1" to="/"
to="/photogallery/" >
>gallery</Link> home
</nav> </Link>
<div className="bg-black min-h-screen mx-auto 2xl:container"> <Link
<h1 className="text-5xl mt-0 ml-5 font-serif font-black z-10 relative">Photo Gallery</h1> className="hover:underline text-vibrant-light hover:text-muted-light mx-1"
<div className="mx-auto"> to="/photogallery/"
<MasonryGallery >
images={images} gallery
itemsPerRow={{ </Link>
sm: 2, </nav>
md: 2, <div className="bg-black min-h-screen mx-auto 2xl:container">
lg: 3, <h1 className="text-5xl mt-0 ml-5 font-serif font-black z-10 relative">
xl: 3, Photo Gallery
'2xl': 4, </h1>
}} <div className="mx-auto">
/> <MasonryGallery
images={images}
itemsPerRow={{
sm: 2,
md: 2,
lg: 3,
xl: 3,
"2xl": 4,
}}
/>
</div>
</div> </div>
</div> </>
</>); );
}; };
export const query = graphql` export const query = graphql`
query GalleryPageQuery { query GalleryPageQuery {
allFile( allFile(
filter: { sourceInstanceName: { eq: "gallery" } } filter: { sourceInstanceName: { eq: "gallery" } }
sort: {order: DESC, fields: fields___imageMeta___dateTaken} sort: { order: DESC, fields: fields___imageMeta___dateTaken }
) { ) {
edges { edges {
node { node {
relativePath relativePath
base base
childImageSharp{ childImageSharp {
fluid { fluid {
aspectRatio aspectRatio
}
gatsbyImageData(layout: CONSTRAINED, height: 550)
} }
gatsbyImageData( fields {
layout: CONSTRAINED imageMeta {
height: 550 dateTaken
) iptc {
} object_name
fields { }
imageMeta {
dateTaken
iptc {
object_name
} }
} }
} }
} }
} }
} }
}`; `;
export default GalleryPage; export default GalleryPage;

View File

@ -1,6 +1,6 @@
/* @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital@0;1&display=swap'); */ /* @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital@0;1&display=swap'); */
/* black, bold, regular */ /* black, bold, regular */
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&display=swap'); @import url("https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;0,900;1,400;1,700;1,900&display=swap");
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@ -30,7 +30,7 @@
scroll-snap-align: start; scroll-snap-align: start;
} }
.scroll-padding-6 { .scroll-padding-6 {
scroll-padding: theme('spacing.6'); scroll-padding: theme("spacing.6");
} }
@variants responsive { @variants responsive {
.h-two-thirds-vw { .h-two-thirds-vw {
@ -63,7 +63,7 @@ a {
margin-left: 3px; margin-left: 3px;
transform: translate(0px); transform: translate(0px);
display: inline-block; display: inline-block;
transition: all .2s; transition: all 0.2s;
} }
.arrow-left-before:before { .arrow-left-before:before {

View File

@ -3,4 +3,3 @@
@apply text-3xl font-bold; @apply text-3xl font-bold;
} }
} }

View File

@ -2,31 +2,36 @@
export const getMeta = (image) => image.fields.imageMeta; export const getMeta = (image) => image.fields.imageMeta;
export const getName = (image) => getMeta(image)?.iptc.object_name || image.base; export const getName = (image) =>
getMeta(image)?.iptc.object_name || image.base;
// some pleasing default colors for SSR and initial hydration // some pleasing default colors for SSR and initial hydration
export const getVibrant = (image) => getMeta(image)?.vibrant; export const getVibrant = (image) => getMeta(image)?.vibrant;
export const hasName = (image) => Boolean(getMeta(image)?.iptc.object_name); export const hasName = (image) => Boolean(getMeta(image)?.iptc.object_name);
export const getAspectRatio = (image) => image.childImageSharp.fluid.aspectRatio; export const getAspectRatio = (image) =>
image.childImageSharp.fluid.aspectRatio;
export const getRgba = (palette, alpha) => `rgba(${palette[0]}, ${palette[1]}, ${palette[2]}, ${alpha || 1})`; export const getRgba = (palette, alpha) =>
`rgba(${palette[0]}, ${palette[1]}, ${palette[2]}, ${alpha || 1})`;
// work around SSR bug in react-helmet // work around SSR bug in react-helmet
export const getVibrantToHelmetSafeBodyStyle = (vibrant) => { export const getVibrantToHelmetSafeBodyStyle = (vibrant) => {
const style = { const style = {
'--muted': vibrant.Muted, "--muted": vibrant.Muted,
'--dark-muted': vibrant.DarkMuted, "--dark-muted": vibrant.DarkMuted,
'--light-muted': vibrant.LightMuted, "--light-muted": vibrant.LightMuted,
'--vibrant': vibrant.Vibrant, "--vibrant": vibrant.Vibrant,
'--dark-vibrant': vibrant.DarkVibrant, "--dark-vibrant": vibrant.DarkVibrant,
'--light-vibrant': vibrant.LightVibrant, "--light-vibrant": vibrant.LightVibrant,
}; };
if (typeof window === 'undefined') { if (typeof window === "undefined") {
return style; return style;
} }
return Object.keys(style).map(key => `${(key)}: ${style[key]}`).join(';'); return Object.keys(style)
.map((key) => `${key}: ${style[key]}`)
.join(";");
}; };
const gcd = (a, b) => { const gcd = (a, b) => {

View File

@ -6,7 +6,8 @@
<body> <body>
<h1>contenteditable can be applied to visible style tags</h1> <h1>contenteditable can be applied to visible style tags</h1>
<p contenteditable="true"> <p contenteditable="true">
This paragraph is editable, as is the style tag below. Edit it to see the layout change in real time. This paragraph is editable, as is the style tag below. Edit it to see the
layout change in real time.
</p> </p>
<pre> <pre>
<style contenteditable="true"> <style contenteditable="true">

View File

@ -1,124 +1,119 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head> <meta charset="UTF-8" />
<meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Test Canvas Game</title> <title>Test Canvas Game</title>
<style> <style>
body { body {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
</style> </style>
</head> </head>
<body> <body>
<canvas id="gc"> <canvas id="gc"> </canvas>
</canvas>
<script> <script>
//setup //setup
c = document.getElementById("gc"); c = document.getElementById("gc");
cc = c.getContext('2d'); cc = c.getContext("2d");
c.height = window.innerHeight; c.height = window.innerHeight;
c.width = window.innerWidth; c.width = window.innerWidth;
c.addEventListener('mousemove', function(e) { c.addEventListener("mousemove", function (e) {
p1y = e.clientY - ph / 2; p1y = e.clientY - ph / 2;
}); });
lastTime = Date.now(); lastTime = Date.now();
deltaTime = 0; deltaTime = 0;
fps = 0; fps = 0;
//gameplay variables //gameplay variables
p1y = p2y = 40; p1y = p2y = 40;
pt = 10; pt = 10;
ph = 100; ph = 100;
bx = by = xv = yv = 500;//just making sure bx and by exist, they'll be reset when the game first starts bx = by = xv = yv = 500; //just making sure bx and by exist, they'll be reset when the game first starts
bd = 12; bd = 12;
score1 = score2 = 0; score1 = score2 = 0;
ais = 5; ais = 5;
function reset() {
bx = c.width / 2;
by = c.height / 2;
xv = -xv;
yv = 4;
}
function update() {
function reset() { //measure timing
bx = c.width / 2; now = Date.now();
by = c.height / 2; deltaTime = (now - lastTime) / 1000;
lastTime = now;
fps = 1 / deltaTime;
//move ball
bx += xv * deltaTime;
by += yv * deltaTime;
if (by < 0 && yv < 0) {
yv = -yv;
}
if (by > c.height && yv > 0) {
yv = -yv;
}
//check collision with sides for scoring
if (bx > c.width) {
if (by > p2y && by < p2y + ph) {
xv = -xv; xv = -xv;
yv = 4; dy = by - (p2y + ph / 2);
yv = dy * 10;
} else {
score1++;
reset();
}
} }
if (bx < 0) {
function update() { if (by > p1y && by < p1y + ph) {
//measure timing xv = -xv;
now = Date.now(); dy = by - (p1y + ph / 2);
deltaTime = (now-lastTime)/1000; yv = dy * 10; //.3
lastTime = now; } else {
fps = 1/deltaTime; score2++;
//move ball reset();
bx += xv * deltaTime; }
by += yv * deltaTime;
if (by < 0 && yv < 0) {
yv = -yv;
}
if (by > c.height && yv > 0) {
yv = -yv;
}
//check collision with sides for scoring
if (bx > c.width) {
if (by > p2y && by < p2y + ph) {
xv = -xv;
dy = by - (p2y + ph / 2);
yv = dy * 10;
} else {
score1++;
reset();
}
}
if (bx < 0) {
if (by > p1y && by < p1y + ph) {
xv = -xv;
dy = by - (p1y + ph / 2);
yv = dy * 10;//.3
} else {
score2++
reset();
}
}
//ai
if (p2y + ph / 2 < by) {
p2y += ais;
} else {
p2y -= ais;
}
//draw background
var gradient=cc.createLinearGradient(0,0,c.width,c.height);
gradient.addColorStop("0","#ff146e");
gradient.addColorStop("1","#145aff");
cc.fillStyle=gradient;
// cc.fillStyle="white";
cc.fillRect(0, 0, c.width, c.height);
//draw paddles
cc.fillStyle = 'cyan';
cc.fillRect(0, p1y, pt, ph);
cc.fillStyle = 'red';
cc.fillRect(c.width - pt, p2y, pt, ph);
//draw ball
cc.fillStyle = 'lightgreen';
cc.fillRect(bx - bd / 2, by - bd / 2, bd, bd);
//draw scores
cc.fillStyle = 'white';
cc.font = '20px Times'
cc.fillText(score1, 100, 100);
cc.fillText(score2, c.width - 100, 100);
//draw framerate
cc.fillText("framerate: " + fps, c.width/2 - 100, 100);
window.requestAnimationFrame(update); //Keep the game running
} }
reset() //prepare the game //ai
update() //start the game if (p2y + ph / 2 < by) {
p2y += ais;
} else {
p2y -= ais;
}
//draw background
var gradient = cc.createLinearGradient(0, 0, c.width, c.height);
gradient.addColorStop("0", "#ff146e");
gradient.addColorStop("1", "#145aff");
cc.fillStyle = gradient;
// cc.fillStyle="white";
cc.fillRect(0, 0, c.width, c.height);
//draw paddles
cc.fillStyle = "cyan";
cc.fillRect(0, p1y, pt, ph);
cc.fillStyle = "red";
cc.fillRect(c.width - pt, p2y, pt, ph);
//draw ball
cc.fillStyle = "lightgreen";
cc.fillRect(bx - bd / 2, by - bd / 2, bd, bd);
//draw scores
cc.fillStyle = "white";
cc.font = "20px Times";
cc.fillText(score1, 100, 100);
cc.fillText(score2, c.width - 100, 100);
//draw framerate
cc.fillText("framerate: " + fps, c.width / 2 - 100, 100);
window.requestAnimationFrame(update); //Keep the game running
}
reset(); //prepare the game
update(); //start the game
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,19 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head> <meta charset="UTF-8" />
<meta charset="UTF-8">
<title>Document</title> <title>Document</title>
<link rel="stylesheet" href="link/to/css"> <link rel="stylesheet" href="link/to/css" />
</head> </head>
<body> <body>
<a href="">a tags are for links</a> <a href="">a tags are for links</a>
<p>p tags are for paragraphs</p> <p>p tags are for paragraphs</p>
<h1>h1 through h6 are for headers</h1> <h1>h1 through h6 are for headers</h1>
<img src="path/to/image.jpg" alt="imgs are for images"> <img src="path/to/image.jpg" alt="imgs are for images" />
<div>divs are just boxes</div> <div>divs are just boxes</div>
</body>
</body>
</html> </html>

View File

@ -1,77 +1,101 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head> <meta charset="UTF-8" />
<meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Robotic Chuck</title> <title>Robotic Chuck</title>
<style> <style>
body { body {
margin: auto; margin: auto;
max-width: 800px; max-width: 800px;
line-height: 1.5; line-height: 1.5;
} }
code, code,
#output { #output {
padding: 5px; padding: 5px;
background: #eee; background: #eee;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 3px; border-radius: 3px;
display: inline-block; display: inline-block;
} }
p { p {
max-width: 800px; max-width: 800px;
} }
input, input,
textarea { textarea {
width: 100%; width: 100%;
padding: 5px; padding: 5px;
/*max-width: 800px;*/ /*max-width: 800px;*/
} }
#genbutton { #genbutton {
color: white; color: white;
background: #01C106; background: #01c106;
padding: 1em; padding: 1em;
border: 1px solid #1E7931; border: 1px solid #1e7931;
font-size: 1em; font-size: 1em;
border-radius: 3px; border-radius: 3px;
} }
</style> </style>
</head> </head>
<body> <body>
<script> <script>
console.log("load"); console.log("load");
function generate() { function generate() {
console.log("go!"); console.log("go!");
var inBox = document.getElementById("in"); var inBox = document.getElementById("in");
console.log(inBox.value); console.log(inBox.value);
var links = JSON.parse(inBox.value); var links = JSON.parse(inBox.value);
var outBox = document.getElementById("output"); var outBox = document.getElementById("output");
var titleBox = document.getElementById("titleIn"); var titleBox = document.getElementById("titleIn");
var descBox = document.getElementById("description"); var descBox = document.getElementById("description");
var tstring = "<style>.here,.invmenu a{font-style:italic}.here{font-size:14px;font-weight:400;color:grey}.invmenu{padding:5px 20px;margin:10px 0}</style>\n"; var tstring =
tstring = tstring + "<" + "div class=\"invmenu\" style=\"max-width:100%;background: #f2f2f2;\">\n<h3 style=\"text-align:left;\">" + titleBox.value + "</h3>\n<p><em>"; "<style>.here,.invmenu a{font-style:italic}.here{font-size:14px;font-weight:400;color:grey}.invmenu{padding:5px 20px;margin:10px 0}</style>\n";
tstring = tstring + descBox.value + "</em></p>\n<hr style=\"border: 1px solid #cfcfcf\">\n" tstring =
formatter = links.map((object) => "<h4><a href=\"" + object.url + "\">" + object.headline + "</a></h4>\n"); tstring +
tstring = tstring + formatter.join(""); "<" +
tstring = tstring + "</div>" 'div class="invmenu" style="max-width:100%;background: #f2f2f2;">\n<h3 style="text-align:left;">' +
console.log(tstring); titleBox.value +
outBox.innerText = tstring; "</h3>\n<p><em>";
outBox.style.minHeight = (formatter.length + 9) + "em"; tstring =
document.getElementById("preview").innerHTML = tstring; tstring +
} descBox.value +
'</em></p>\n<hr style="border: 1px solid #cfcfcf">\n';
formatter = links.map(
(object) =>
'<h4><a href="' +
object.url +
'">' +
object.headline +
"</a></h4>\n"
);
tstring = tstring + formatter.join("");
tstring = tstring + "</div>";
console.log(tstring);
outBox.innerText = tstring;
outBox.style.minHeight = formatter.length + 9 + "em";
document.getElementById("preview").innerHTML = tstring;
}
</script> </script>
<p></p> <p></p>
<p>Title of box:</p> <input type="text" id="titleIn" placeholder="Investigating Hope: The Series"> <p>Title of box:</p>
<input
type="text"
id="titleIn"
placeholder="Investigating Hope: The Series"
/>
<p>Description text:</p> <p>Description text:</p>
<input type="text" id="description" placeholder="This article is one in a series of investigative pieces about a complaint filed with ASU regarding accusations against on-campus ministry Hope Church."> <input
type="text"
id="description"
placeholder="This article is one in a series of investigative pieces about a complaint filed with ASU regarding accusations against on-campus ministry Hope Church."
/>
<p>Enter the links to generate according to this format:</p> <p>Enter the links to generate according to this format:</p>
<pre><code class="json">[ <pre><code class="json">[
{ {
@ -87,20 +111,37 @@
"url": "yet-another-url" "url": "yet-another-url"
} }
]</code></pre> ]</code></pre>
<p>This format is known as JSON, it's an internet standard that's supposed to be easy for humans and computers to read. <p>
Note how commas are used to separate multiple items but are never used after the last item in a set. This is important, This format is known as JSON, it's an internet standard that's supposed to
it will not work if you include a trailing comma where there should not be one.</p> be easy for humans and computers to read. Note how commas are used to
<textarea name="input" id="in" cols="100" rows="30" placeholder="Please be careful, this will not work if you format the JSON incorrectly"></textarea> separate multiple items but are never used after the last item in a set.
<p><a id="genbutton" href="#output" onclick="generate()">Generate Code</a></p> This is important, it will not work if you include a trailing comma where
there should not be one.
</p>
<textarea
name="input"
id="in"
cols="100"
rows="30"
placeholder="Please be careful, this will not work if you format the JSON incorrectly"
></textarea>
<p>
<a id="genbutton" href="#output" onclick="generate()">Generate Code</a>
</p>
<p>Paste the following code into a safeembed</p> <p>Paste the following code into a safeembed</p>
<textarea id="output" cols="100" readonly></textarea> <textarea id="output" cols="100" readonly></textarea>
<p><em> Don't forget:</em> For each page you embed on, add the following bit of code just before the <code>&lt;/h4&gt;</code> at the end of the line on the line that corresponds to the current page.</p> <p>
<em> Don't forget:</em> For each page you embed on, add the following bit
of code just before the <code>&lt;/h4&gt;</code> at the end of the line on
the line that corresponds to the current page.
</p>
<pre><code>&lt;span class="here"&gt;(You are here)&lt;/span&gt;</code></pre> <pre><code>&lt;span class="here"&gt;(You are here)&lt;/span&gt;</code></pre>
<p>The code generated will look roughly like this:</P> <p>The code generated will look roughly like this:</p>
<div id="preview" style="max-width: 800px;"></div> <div id="preview" style="max-width: 800px"></div>
<p>Gryphon overrides certain styles by default, so make sure to test on your article.</p> <p>
Gryphon overrides certain styles by default, so make sure to test on your
</body> article.
</p>
</body>
</html> </html>

View File

@ -1,34 +1,34 @@
const defaultTheme = require('tailwindcss/defaultTheme'); const defaultTheme = require("tailwindcss/defaultTheme");
module.exports = { module.exports = {
purge: ['./src/**/*.{js,jsx,ts,tsx}'], purge: ["./src/**/*.{js,jsx,ts,tsx}"],
// darkMode: 'media', // or 'media' or 'class' // darkMode: 'media', // or 'media' or 'class'
theme: { theme: {
screens: { screens: {
'sm': '640px', sm: "640px",
'md': '768px', md: "768px",
'lg': '1024px', lg: "1024px",
'xl': '1280px', xl: "1280px",
'2xl': '1536px', "2xl": "1536px",
'portrait': {'raw': '(orientation: portrait)'}, portrait: { raw: "(orientation: portrait)" },
'landscape': {'raw': '(orientation: landscape)'}, landscape: { raw: "(orientation: landscape)" },
}, },
spacing: { spacing: {
'0': '0px', 0: "0px",
'1': '4px', 1: "4px",
'2': '8px', 2: "8px",
'3': '12px', 3: "12px",
'4': '16px', 4: "16px",
'5': '24px', 5: "24px",
'6': '32px', 6: "32px",
'7': '48px', 7: "48px",
'8': '80px', 8: "80px",
'9': '800px', 9: "800px",
}, },
fontFamily: { fontFamily: {
...defaultTheme.fontFamily, ...defaultTheme.fontFamily,
// serif: ['Didot', 'Didot LT', 'STD', 'Hoefler Text' , 'Garamond', 'Times New Roman', 'serif'] // serif: ['Didot', 'Didot LT', 'STD', 'Hoefler Text' , 'Garamond', 'Times New Roman', 'serif']
serif: ['Playfair Display', 'serif'], serif: ["Playfair Display", "serif"],
}, },
extend: { extend: {
colors: { colors: {
@ -40,7 +40,7 @@ module.exports = {
if (opacityVariable !== undefined) { if (opacityVariable !== undefined) {
return `rgba(var(--vibrant), var(${opacityVariable}, 1))`; return `rgba(var(--vibrant), var(${opacityVariable}, 1))`;
} }
return 'rgb(var(--vibrant))'; return "rgb(var(--vibrant))";
}, },
light: ({ opacityVariable, opacityValue }) => { light: ({ opacityVariable, opacityValue }) => {
if (opacityValue !== undefined) { if (opacityValue !== undefined) {
@ -49,7 +49,7 @@ module.exports = {
if (opacityVariable !== undefined) { if (opacityVariable !== undefined) {
return `rgba(var(--light-vibrant), var(${opacityVariable}, 1))`; return `rgba(var(--light-vibrant), var(${opacityVariable}, 1))`;
} }
return 'rgb(var(--light-vibrant))'; return "rgb(var(--light-vibrant))";
}, },
dark: ({ opacityVariable, opacityValue }) => { dark: ({ opacityVariable, opacityValue }) => {
if (opacityValue !== undefined) { if (opacityValue !== undefined) {
@ -58,7 +58,7 @@ module.exports = {
if (opacityVariable !== undefined) { if (opacityVariable !== undefined) {
return `rgba(var(--dark-vibrant), var(${opacityVariable}, 1))`; return `rgba(var(--dark-vibrant), var(${opacityVariable}, 1))`;
} }
return 'rgb(var(--dark-vibrant))'; return "rgb(var(--dark-vibrant))";
}, },
}, },
muted: { muted: {
@ -69,7 +69,7 @@ module.exports = {
if (opacityVariable !== undefined) { if (opacityVariable !== undefined) {
return `rgba(var(--muted), var(${opacityVariable}, 1))`; return `rgba(var(--muted), var(${opacityVariable}, 1))`;
} }
return 'rgb(var(--muted))'; return "rgb(var(--muted))";
}, },
light: ({ opacityVariable, opacityValue }) => { light: ({ opacityVariable, opacityValue }) => {
if (opacityValue !== undefined) { if (opacityValue !== undefined) {
@ -78,7 +78,7 @@ module.exports = {
if (opacityVariable !== undefined) { if (opacityVariable !== undefined) {
return `rgba(var(--light-muted), var(${opacityVariable}, 1))`; return `rgba(var(--light-muted), var(${opacityVariable}, 1))`;
} }
return 'rgb(var(--light-muted))'; return "rgb(var(--light-muted))";
}, },
dark: ({ opacityVariable, opacityValue }) => { dark: ({ opacityVariable, opacityValue }) => {
if (opacityValue !== undefined) { if (opacityValue !== undefined) {
@ -87,7 +87,7 @@ module.exports = {
if (opacityVariable !== undefined) { if (opacityVariable !== undefined) {
return `rgba(var(--dark-muted), var(${opacityVariable}, 1))`; return `rgba(var(--dark-muted), var(${opacityVariable}, 1))`;
} }
return 'rgb(var(--dark-muted))'; return "rgb(var(--dark-muted))";
}, },
}, },
}, },