Full Code of daneden/photos.daneden.me for AI

main fc206dc7968c cached
22 files
18.8 KB
5.5k tokens
12 symbols
1 requests
Download .txt
Repository: daneden/photos.daneden.me
Branch: main
Commit: fc206dc7968c
Files: 22
Total size: 18.8 KB

Directory structure:
gitextract_sr7bdbz4/

├── .eslintrc
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── automerge.yml
│       └── main.yml
├── .gitignore
├── .husky/
│   └── .gitignore
├── .prettierrc.js
├── README.md
├── components/
│   ├── App.tsx
│   ├── GlobalStyles.tsx
│   ├── Header.tsx
│   ├── Image.tsx
│   └── types.d.ts
├── data/
│   ├── altDescriptions.json
│   └── meta.tsx
├── hooks/
│   └── useMatchMedia.ts
├── next-env.d.ts
├── package.json
├── pages/
│   ├── _document.tsx
│   └── index.tsx
├── scripts/
│   └── exif.js
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc
================================================
{
  "extends": "next"
}


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
  directory: "/"
  schedule:
    interval: daily


================================================
FILE: .github/workflows/automerge.yml
================================================
name: Enable automerge for dependabot PRs

on:
  pull_request_target:

jobs:
  merge-me:
    name: Enable automerge for dependabot PRs
    runs-on: ubuntu-latest
    steps:
      - name: Enable automerge for dependabot PRs
        uses: daneden/enable-automerge-action@v1
        with:
          github-token: ${{ secrets.PAT }}


================================================
FILE: .github/workflows/main.yml
================================================
name: Create issues from todos

on:
  push:
    branches:
      - master

jobs:
  todos:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v1
      - name: todo-actions
        uses: dtinth/todo-actions@master
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          TODO_ACTIONS_MONGO_URL: ${{ secrets.TODO_ACTIONS_MONGO_URL }}


================================================
FILE: .gitignore
================================================
# See http://help.github.com/ignore-files/ for more about ignoring files.

# dependencies
node_modules
npm-shrinkwrap.json
package-lock.json

# production
build
.next

# misc
.DS_Store
npm-debug.log
yarn-error.log

# ignore generated data
package-lock.json
data/manifest.ts
.now

================================================
FILE: .husky/.gitignore
================================================
_


================================================
FILE: .prettierrc.js
================================================
module.exports = {
  semi: false,
  trailingComma: "es5",
}


================================================
FILE: README.md
================================================
# photos.daneden.me

A place for my photos to shine (in more glory than Instagram can deliver).

## How does this work?

I used the amazing [create-react-app](https://github.com/facebookincubator/create-react-app) as a starting point for this site, and added a few things to make it my own.

- Images are uploaded to the `public/images` folder
- Next, there's the pre-start script [`exif.js`](https://github.com/daneden/photos.daneden.me/blob/master/scripts/exif.js). This Node script uses `node-exiftool` to loop over each image in the folder and extract exif data. The particular data I wanted to display was the aperture, shutter speed, ISO, and focal length. This data is dropped into `manifest.ts`, which is ignored by git to avoid too many sources of truth (in this case, the images remain the sources of truth).
- [`index.tsx`](https://github.com/daneden/photos.daneden.me/blob/master/src/index.tsx) is what imports the data from `manifest.ts` and passes it as props to the images, which are rendered as React components.


================================================
FILE: components/App.tsx
================================================
import { ReactElement, ReactNode } from "react"
import altDescriptions from "../data/altDescriptions.json"
import GlobalStyles from "./GlobalStyles"
import Header from "./Header"
import Image, { Props as ImageProps } from "./Image"

type Props = {
  preface?: ReactElement
  images: Array<ImageProps>
}

function Preface({ children }: { children: ReactNode }): ReactElement {
  return (
    <>
      <div className="pane pane--text">
        <GlobalStyles />
        <Header />
        {children}
      </div>
      <style jsx>{`
        @media (orientation: landscape) {
          .pane {
            position: sticky;
            left: 0;
            top: 0;
            transform: translateY(
              calc(
                max(
                  min(var(--scroll-delta), 300) / 300 * var(--baseline) * -2,
                  -2rem
                )
              )
            );
          }
        }
      `}</style>
    </>
  )
}

function App(props: Props): ReactElement {
  return (
    <>
      <main className="site-content">
        <Preface>
          <div className="content">{props.preface}</div>
        </Preface>
        {props.images.map((img, i) => (
          <Image
            key={i}
            aspectRatio={img.aspectRatio}
            camera={img.camera}
            fStop={img.fStop}
            focalLength={img.focalLength}
            iso={img.iso}
            name={img.name}
            speed={img.speed}
            alt={altDescriptions[img.name]}
            width={img.width}
            height={img.height}
          />
        ))}
      </main>
      <style jsx>{`
        .content {
          opacity: calc((200 - var(--scroll-delta)) / 200);
        }
      `}</style>
    </>
  )
}

export default App


================================================
FILE: components/GlobalStyles.tsx
================================================
export default function GlobalStyles() {
  return (
    <style jsx global>
      {`
        :root {
          --imgSize: 85vh;
          --baseline: 1.5rem;
          --darkGray: #222;
          --lightGray: #fefefe;
          --foreground: var(--darkGray);
          --background: var(--lightGray);
        }

        * {
          padding: 0;
          margin: 0;
          box-sizing: border-box;
          position: relative;
        }

        html {
          -webkit-text-size-adjust: none;
          background-color: var(--background);
          color: var(--foreground);
          font: 80%/1.5 system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
          height: 100%;
          overflow: hidden;
          transition: 0.3s ease;
          transition-property: color, background-color;
        }

        @media (prefers-color-scheme: dark) {
          html {
            background-color: var(--foreground);
            color: var(--background);
          }
        }

        body {
          display: flex;
          height: 100%;
          align-items: center;
          overflow: scroll;
          -webkit-overflow-scrolling: touch;
        }

        @media (orientation: portrait) {
          html {
            overflow: initial;
          }

          body {
            height: auto;
          }
        }

        a {
          color: inherit;
          text-decoration-color: var(--highlight);
          transition: 0.2s ease;
          transition-property: color, text-decoration-color;
        }

        a:hover,
        a:focus {
          color: var(--highlight);
        }

        p {
          margin-bottom: calc(var(--baseline) / 2);
        }

        .frac {
          font-variant-numeric: diagonal-fractions;
        }

        .site-title {
          font-weight: 600;
          font-size: 1rem;
        }

        #__next {
          display: flex;
          flex-flow: column nowrap;
          padding-bottom: var(--baseline);
        }

        .site-content {
          flex: 1 0 auto;
          display: grid;
          max-height: var(--imgSize);
          grid-auto-flow: column;
          align-items: stretch;
        }

        .pane {
          flex: 1 0 auto;
          width: auto;
          vertical-align: top;
          padding: calc(var(--baseline) / 2);
          display: flex;
          flex-direction: column;
        }

        @media (orientation: portrait) {
          .site-root {
            flex-flow: column wrap;
            max-height: unset;
          }

          .site-content {
            grid-auto-flow: row;
            width: 100px;
          }

          .pane {
            height: auto;
            width: auto;
            margin: 0;
            max-width: 100vw;
          }
        }

        .pane--text {
          align-self: flex-start;
          width: 28rem;
          max-width: 100%;
        }

        @media (orientation: portrait) {
          .pane--text {
            flex-basis: auto;
            width: 100vw;
          }
        }

        .placeholder,
        .image__img.ssr {
          --aspect-ratio: 1;
          background-color: rgba(0, 0, 0, 0.1);
          max-height: var(--imgSize);
          height: var(--imgSize);
          width: calc(var(--imgSize) * var(--aspect-ratio));
        }

        @media (orientation: portrait) {
          .placeholder,
          .image__img.ssr {
            --width: calc(100vw - var(--baseline));
            width: var(--width);
            height: calc(var(--width) / var(--aspect-ratio));
          }
        }
      `}
    </style>
  )
}


================================================
FILE: components/Header.tsx
================================================
import { ReactElement } from "react"

const Header = (): ReactElement => {
  return (
    <header className="site-header">
      <h1 className="site-title">
        photos.<a href="https://daneden.me">daneden.me</a>
      </h1>
    </header>
  )
}

export default Header


================================================
FILE: components/Image.tsx
================================================
import NextImage from "next/image"
import * as React from "react"

const { useEffect, useState } = React

export type Props = {
  aspectRatio: number
  camera: string
  alt: string
  focalLength: string
  fStop: number
  iso: number
  name: string
  speed: string
  width: number
  height: number
}

function Image(props: Props) {
  const [imageLoaded, setImageLoaded] = useState(false)

  const {
    aspectRatio,
    camera,
    alt: description,
    fStop,
    focalLength,
    iso,
    name,
    width,
    height,
  } = props

  const url = `/images/${name}`

  const image = (
    <NextImage
      alt={description}
      className={`image ${imageLoaded ? "loaded" : "not-loaded"}`}
      height={height}
      onLoad={() => setImageLoaded(true)}
      src={url}
      width={width}
    />
  )

  const speed =
    // If the shutter speed is a fraction, we want to style it appropriately.
    props.speed.includes("/") ? (
      <span className="frac">{props.speed}</span>
    ) : (
      props.speed
    )

  return (
    <>
      <div className="pane">
        <div className="image-container">{image}</div>
        <p>
          {camera}, {`\u0192${fStop}, `}
          {speed} sec, {focalLength}, <span className="caps">ISO</span> {iso}
        </p>
      </div>
      <style jsx>{`
        .pane {
          --aspect-ratio: ${aspectRatio};
          display: flex;
          flex: 1 1 100%;
          transition: 0.5s ease;
          transition-property: transform, opacity;
        }

        .image-container {
          height: var(--imgSize);
          width: calc(var(--imgSize) * var(--aspect-ratio));
        }

        .pane :global(.image) {
          border-radius: 4px;
          display: block;
          flex: 0 0 100%;
          object-fit: cover;
          object-position: center;
          transition: 0.3s ease opacity;
          opacity: 1;
          background-color: rgba(0, 0, 0, 0.15);
          height: var(--imgSize);
          max-width: 100%;
        }

        @media (orientation: portrait) {
          .pane {
            height: auto;
            width: auto;
          }

          .image-container {
            width: 100%;
            height: auto;
          }
        }
      `}</style>
    </>
  )
}

export default Image


================================================
FILE: components/types.d.ts
================================================
declare module "*.woff2"
declare module "*.woff"


================================================
FILE: data/altDescriptions.json
================================================
{
  "00013.jpg": "A man standing in a warmly-lit room and taking a photograph of something out of frame. Behind him is a long, daylight-drenched and spacious corridor. Close by, a woman stares at something else out of frame, and in the very far distance, green trees can be seen through a frosted window.",
  "00015.jpg": "A woman crouches near a body of water at sunrise to take a photograph of a city in the distance.",
  "00016.jpg": "The interior of Chicago’s Natural History Museum, looking down on tourists in the atrium. A group of people stand around a Tyrannosaurus Rex skeleton, looking at a map.",
  "00021.jpg": "A brown horse eating grass on a sunny day. In the distant background, an earthy hill is peppered with small buildings, just beyond a shallow body of water.",
  "00028.jpg": "",
  "00030.jpg": "",
  "00031.jpg": "",
  "00034.jpg": "",
  "00035.jpg": "",
  "00036.jpg": "",
  "00037.jpg": "",
  "00039.jpg": "",
  "00041.jpg": "",
  "00043.jpg": "",
  "00045.jpg": "",
  "00046.jpg": "",
  "00047.jpg": "",
  "00048.jpg": "",
  "00049.jpg": "",
  "00050.jpg": "",
  "00051.jpg": "",
  "00052.jpg": "",
  "00053.jpg": "",
  "00054.jpg": "The exterior of a modern residential building in Barcelona, with concrete balconies overflowing with vibrant green plants.",
  "00055.jpg": "The interior of London’s Natural History Museum’s atrium. Light cascades in through windows in the ceiling; a blue whale’s skeleton is in the center of the frame, with dozens of people walking around the atrium and looking at the exhibits.",
  "00056.jpg": "A field of wildflowers; vibrant spots of red poppies, yellow, lilac, and purple flowers pepper a green field."
}


================================================
FILE: data/meta.tsx
================================================
const siteInfo = {
  title: "Dan Eden \u2014 Photos",
  description:
    "Photography by Daniel Eden, a designer living in Manchester, UK.",
  fullDescription: (
    <>
      <p>
        Dan Eden is a Designer from Manchester, England. He prefers to talk in
        the first person.
      </p>

      <p>
        Amongst thousands of photos, it&rsquo;s easy to lose track of my
        favorites. This little website serves as a home for the photos I&rsquo;m
        most proud of.
      </p>

      <p>
        You can follow me on <a href="https://twitter.com/_dte">Twitter</a> and{" "}
        <a href="https://instagram.com/_dte">Instagram</a>.
      </p>
    </>
  ),
  image: "https://photos.daneden.me/images/00013.jpg",
}

export default siteInfo


================================================
FILE: hooks/useMatchMedia.ts
================================================
import { useEffect, useState } from "react"

export default function useMatchMedia(query) {
  const mq = window.matchMedia !== undefined ? window.matchMedia(query) : null
  const [matches, setMatches] = useState(mq !== null ? mq.matches : false)

  useEffect(() => {
    function updateMqMatches(mediaQueryList) {
      setMatches(mediaQueryList.matches)
    }

    if (mq !== null) {
      mq.addListener(updateMqMatches)

      return () => {
        mq.removeListener(updateMqMatches)
      }
    }
  }, [mq])

  return matches
}


================================================
FILE: next-env.d.ts
================================================
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.


================================================
FILE: package.json
================================================
{
  "name": "photos",
  "version": "0.0.1",
  "private": true,
  "devDependencies": {
    "@types/node": "^18.7.14",
    "@types/react": "^18.0.18",
    "@types/react-dom": "^18.0.6",
    "eslint": "^8.52.0",
    "eslint-config-next": "^14.0.0",
    "husky": "8.0.1",
    "lint-staged": "13.0.3",
    "prettier": "2.7.1",
    "typescript": "^5.2.2"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,json,css,md}": [
      "prettier --write"
    ]
  },
  "dependencies": {
    "dist-exiftool": "10.53.0",
    "next": "^14.0.0",
    "node-exiftool": "2.3.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "scripts": {
    "prestart": "node ./scripts/exif.js",
    "prebuild": "node ./scripts/exif.js",
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}


================================================
FILE: pages/_document.tsx
================================================
import Document, { Head, Html, Main, NextScript } from "next/document"

class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument


================================================
FILE: pages/index.tsx
================================================
import Head from "next/head"
import React, { useEffect } from "react"
import App from "../components/App"
import imageData from "../data/manifest"
import siteInfo from "../data/meta"

const images = imageData.slice().reverse()

function HomePage() {
  useEffect(() => {
    let content: HTMLElement = document.body
    window.addEventListener("mousewheel", scrollHandler)

    function scrollHandler(e) {
      if (content === undefined) {
        content = document.body
      } else {
        content.scrollLeft += e.deltaY
        content.setAttribute("style", `--scroll-delta: ${content.scrollLeft}`)
      }
    }

    return () => {
      window.removeEventListener("mousewheel", scrollHandler)
    }
  })

  return (
    <>
      <Head>
        <title>{siteInfo.title}</title>
        <meta name="description" content={siteInfo.description} />

        <meta name="viewport" content="width=device-width, initial-scale=1" />

        <meta name="twitter:card" content="summary_large_image" />
        <meta name="twitter:site" content="@_dte" />
        <meta property="og:title" content="Daniel Eden &mdash; Photography" />
        <meta property="og:type" content="website" />
        <meta
          property="og:image"
          content="https://photos.daneden.me/images/00013.jpg"
        />
        <meta property="og:description" content={siteInfo.description} />
      </Head>

      <App preface={siteInfo.fullDescription} images={images} />
    </>
  )
}

export default HomePage


================================================
FILE: scripts/exif.js
================================================
"use strict"
const fs = require("fs")
const exiftool = require("node-exiftool")
const exiftoolBin = require("dist-exiftool")
const ep = new exiftool.ExiftoolProcess(exiftoolBin)

const CAMERAS = {
  SONY: {
    "ILCE-6000": "Sony a6000",
  },
  "LEICA CAMERA AG": {
    "LEICA Q (Typ 116)": "Leica Q",
    "LEICA SL2-S": "Leica SL2-S",
  },
}

async function createManifestFromExifData(exifData) {
  let fileInfo = []

  // Transform the data to remove all but the info we care about
  exifData.data.forEach(async (datum) => {
    // The aspect ratio here is actually in terms of
    // height:width (instead of typical width:height)
    // since they all have a fixed height relative to the
    // viewport
    const [width, height] = datum.ImageSize.split("x").map((n) => parseInt(n))
    const aspectRatio = [width, height].reduce((w, h) => w / h)

    const info = {
      aspectRatio,
      camera: CAMERAS[datum.Make][datum.Model] ?? datum.Make,
      fStop: datum.FNumber || 16,
      name: datum.FileName,
      // I only have one manual lens, but this ternary is a hacky workaround.
      focalLength: datum.FocalLength
        ? datum.FocalLength.replace(" ", "")
        : "12mm",
      iso: datum.ISO,
      speed: String(datum.ShutterSpeed),
      alt: datum.Description || "",
      width,
      height,
    }

    fileInfo.push(info)
  })

  // Sort the image data by filename
  fileInfo.sort((a, b) => {
    let keyA = parseInt(a.name.split(".")[0]),
      keyB = parseInt(b.name.split(".")[0])
    if (keyA < keyB) return -1
    if (keyA > keyB) return 1
    return 0
  })

  // Write data to file for the app to consume
  let writeString = `import { Props as ImageData } from "../components/Image"
const imageData: Array<ImageData> = ${JSON.stringify(fileInfo, null, " ")}
export default imageData`

  fs.writeFile("./data/manifest.ts", writeString, (err) => {
    if (err) return console.log(err)
  })
}

ep.open()
  .then((pid) => {
    console.log(`🏁  Started exiftool process (PID: ${pid})`)
    console.log("📸  Extracting photo metadata...")
    return ep
      .readMetadata("./public/images/")
      .then(async (res) => {
        await createManifestFromExifData(res)
      })
      .catch((error) => {
        console.log("Error: ", error)
      })
  })
  .then(() => {
    return ep.close().then(() => {
      console.log("✅  Metadata extracted! Closing exiftool.")
    })
  })
  .catch((error) => {
    console.error("🚨  Error extracting photo metadata!", error)
  })


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "incremental": true
  },
  "exclude": [
    "node_modules"
  ],
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx"
  ]
}
Download .txt
gitextract_sr7bdbz4/

├── .eslintrc
├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── automerge.yml
│       └── main.yml
├── .gitignore
├── .husky/
│   └── .gitignore
├── .prettierrc.js
├── README.md
├── components/
│   ├── App.tsx
│   ├── GlobalStyles.tsx
│   ├── Header.tsx
│   ├── Image.tsx
│   └── types.d.ts
├── data/
│   ├── altDescriptions.json
│   └── meta.tsx
├── hooks/
│   └── useMatchMedia.ts
├── next-env.d.ts
├── package.json
├── pages/
│   ├── _document.tsx
│   └── index.tsx
├── scripts/
│   └── exif.js
└── tsconfig.json
Download .txt
SYMBOL INDEX (12 symbols across 7 files)

FILE: components/App.tsx
  type Props (line 7) | type Props = {
  function Preface (line 12) | function Preface({ children }: { children: ReactNode }): ReactElement {
  function App (line 41) | function App(props: Props): ReactElement {

FILE: components/GlobalStyles.tsx
  function GlobalStyles (line 1) | function GlobalStyles() {

FILE: components/Image.tsx
  type Props (line 6) | type Props = {
  function Image (line 19) | function Image(props: Props) {

FILE: hooks/useMatchMedia.ts
  function useMatchMedia (line 3) | function useMatchMedia(query) {

FILE: pages/_document.tsx
  class MyDocument (line 3) | class MyDocument extends Document {
    method render (line 4) | render() {

FILE: pages/index.tsx
  function HomePage (line 9) | function HomePage() {

FILE: scripts/exif.js
  constant CAMERAS (line 7) | const CAMERAS = {
  function createManifestFromExifData (line 17) | async function createManifestFromExifData(exifData) {
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (22K chars).
[
  {
    "path": ".eslintrc",
    "chars": 24,
    "preview": "{\n  \"extends\": \"next\"\n}\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 94,
    "preview": "version: 2\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: daily\n"
  },
  {
    "path": ".github/workflows/automerge.yml",
    "chars": 329,
    "preview": "name: Enable automerge for dependabot PRs\n\non:\n  pull_request_target:\n\njobs:\n  merge-me:\n    name: Enable automerge for "
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 367,
    "preview": "name: Create issues from todos\n\non:\n  push:\n    branches:\n      - master\n\njobs:\n  todos:\n    runs-on: ubuntu-latest\n\n   "
  },
  {
    "path": ".gitignore",
    "chars": 278,
    "preview": "# See http://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\nnode_modules\nnpm-shrinkwrap.js"
  },
  {
    "path": ".husky/.gitignore",
    "chars": 2,
    "preview": "_\n"
  },
  {
    "path": ".prettierrc.js",
    "chars": 60,
    "preview": "module.exports = {\n  semi: false,\n  trailingComma: \"es5\",\n}\n"
  },
  {
    "path": "README.md",
    "chars": 1029,
    "preview": "# photos.daneden.me\n\nA place for my photos to shine (in more glory than Instagram can deliver).\n\n## How does this work?\n"
  },
  {
    "path": "components/App.tsx",
    "chars": 1747,
    "preview": "import { ReactElement, ReactNode } from \"react\"\nimport altDescriptions from \"../data/altDescriptions.json\"\nimport Global"
  },
  {
    "path": "components/GlobalStyles.tsx",
    "chars": 3595,
    "preview": "export default function GlobalStyles() {\n  return (\n    <style jsx global>\n      {`\n        :root {\n          --imgSize:"
  },
  {
    "path": "components/Header.tsx",
    "chars": 271,
    "preview": "import { ReactElement } from \"react\"\n\nconst Header = (): ReactElement => {\n  return (\n    <header className=\"site-header"
  },
  {
    "path": "components/Image.tsx",
    "chars": 2270,
    "preview": "import NextImage from \"next/image\"\nimport * as React from \"react\"\n\nconst { useEffect, useState } = React\n\nexport type Pr"
  },
  {
    "path": "components/types.d.ts",
    "chars": 49,
    "preview": "declare module \"*.woff2\"\ndeclare module \"*.woff\"\n"
  },
  {
    "path": "data/altDescriptions.json",
    "chars": 1672,
    "preview": "{\n  \"00013.jpg\": \"A man standing in a warmly-lit room and taking a photograph of something out of frame. Behind him is a"
  },
  {
    "path": "data/meta.tsx",
    "chars": 756,
    "preview": "const siteInfo = {\n  title: \"Dan Eden \\u2014 Photos\",\n  description:\n    \"Photography by Daniel Eden, a designer living "
  },
  {
    "path": "hooks/useMatchMedia.ts",
    "chars": 533,
    "preview": "import { useEffect, useState } from \"react\"\n\nexport default function useMatchMedia(query) {\n  const mq = window.matchMed"
  },
  {
    "path": "next-env.d.ts",
    "chars": 201,
    "preview": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edite"
  },
  {
    "path": "package.json",
    "chars": 1077,
    "preview": "{\n  \"name\": \"photos\",\n  \"version\": \"0.0.1\",\n  \"private\": true,\n  \"devDependencies\": {\n    \"@types/node\": \"^18.7.14\",\n   "
  },
  {
    "path": "pages/_document.tsx",
    "chars": 302,
    "preview": "import Document, { Head, Html, Main, NextScript } from \"next/document\"\n\nclass MyDocument extends Document {\n  render() {"
  },
  {
    "path": "pages/index.tsx",
    "chars": 1496,
    "preview": "import Head from \"next/head\"\nimport React, { useEffect } from \"react\"\nimport App from \"../components/App\"\nimport imageDa"
  },
  {
    "path": "scripts/exif.js",
    "chars": 2497,
    "preview": "\"use strict\"\nconst fs = require(\"fs\")\nconst exiftool = require(\"node-exiftool\")\nconst exiftoolBin = require(\"dist-exifto"
  },
  {
    "path": "tsconfig.json",
    "chars": 600,
    "preview": "{\n  \"compilerOptions\": {\n    \"target\": \"es5\",\n    \"lib\": [\n      \"dom\",\n      \"dom.iterable\",\n      \"esnext\"\n    ],\n    "
  }
]

About this extraction

This page contains the full source code of the daneden/photos.daneden.me GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (18.8 KB), approximately 5.5k tokens, and a symbol index with 12 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!