diff --git a/content/_index.md b/content/_index.md
new file mode 100644
index 0000000..4a2717f
--- /dev/null
+++ b/content/_index.md
@@ -0,0 +1,34 @@
+---
+title: "About me"
+---
+
+I am a Research Software Engineer and the Founder of [CatalystNeuro](http://catalystneuro.com), where I work to transform how neuroscience labs collaborate and share data.
+
+## Vision & Work
+
+At CatalystNeuro, we're revolutionizing neuroscience collaboration through better data standardization and tool sharing. Our work focuses on:
+
+- Developing standardized data formats for neuroscience
+- Creating tools for seamless data sharing between labs
+- Building bridges between different analysis platforms
+- Consulting with labs to optimize their data workflows
+
+We believe the future of neuroscience lies in open collaboration, and we're actively shaping how data and tools are shared across the international neuroscience community.
+
+## Background
+
+I received my Ph.D. in Bioengineering from the [UC Berkeley - UCSF Joint Program in Bioengineering](http://bioegrad.berkeley.edu/), working in [Dr. Edward Chang's lab](http://changlab.ucsf.edu/). My research focused on using electrocorticography (ECoG) to understand speech control in humans, particularly the neural mechanisms of voice pitch control in speaking and singing.
+
+During my undergraduate years at the University of Pittsburgh's [SMILE lab](https://smile.pitt.edu/) under [Dr. Aaron Batista](https://www.engineering.pitt.edu/AaronBatista/), I developed probabilistic models of neural activity. This early work shaped my approach to neural data analysis and eventually led to my interest in standardizing data practices across the field.
+
+## Alternative Paths in Science
+
+I'm passionate about exploring non-traditional careers in science. Through CatalystNeuro, I've found a way to contribute to neuroscience beyond the conventional academic path. I work with a talented team of neuroscientists and software developers who share this vision.
+
+If you're interested in exploring alternative careers in science or want to learn about different paths, feel free to reach out. I'm always happy to share experiences and discuss possibilities.
+
+## Beyond the Lab
+
+When not working on neuroscience data, I enjoy:
+- Dancing West Coast Swing
+- Traveling and exploring new cultures
\ No newline at end of file
diff --git a/content/cv.md b/content/cv.md
new file mode 100644
index 0000000..656f86f
--- /dev/null
+++ b/content/cv.md
@@ -0,0 +1,5 @@
+---
+title: "CV"
+---
+
+
\ No newline at end of file
diff --git a/content/portfolio/_index.md b/content/portfolio/_index.md
new file mode 100644
index 0000000..2c94045
--- /dev/null
+++ b/content/portfolio/_index.md
@@ -0,0 +1,6 @@
+---
+title: "Portfolio"
+layout: list
+---
+
+Project portfolio and featured work.
\ No newline at end of file
diff --git a/content/portfolio/portfolio-1.md b/content/portfolio/portfolio-1.md
new file mode 100644
index 0000000..2b712f9
--- /dev/null
+++ b/content/portfolio/portfolio-1.md
@@ -0,0 +1,4 @@
+---
+title: "Broken axes"
+excerpt: "Package for creating broken axis plots in matplotlib "
+---
\ No newline at end of file
diff --git a/content/portfolio/portfolio-2.md b/content/portfolio/portfolio-2.md
new file mode 100644
index 0000000..d089f1e
--- /dev/null
+++ b/content/portfolio/portfolio-2.md
@@ -0,0 +1,6 @@
+---
+title: "Portfolio item number 2"
+excerpt: "Short description of portfolio item number 2 "
+---
+
+This is an item in your portfolio. It can be have images or nice text. If you name the file .md, it will be parsed as markdown. If you name the file .html, it will be parsed as HTML.
\ No newline at end of file
diff --git a/content/posts/2018-03-29-tenseflow.md b/content/posts/2018-03-29-tenseflow.md
new file mode 100644
index 0000000..76c3f8c
--- /dev/null
+++ b/content/posts/2018-03-29-tenseflow.md
@@ -0,0 +1,41 @@
+---
+title: 'tenseflow'
+date: 2018-03-29
+tags:
+ - python
+---
+
+
+
+I was frustrated while changing the tense of a document, and decided to go down the deep dark rabbit hole of creating an
+ automatic tense changer. The basic usage is:
+
+ ```python
+from tenseflow import change_tense
+
+change_tense('I will go to the store.', 'past')
+u'I went to the store.'
+```
+
+Little did I know, this is a really tough task, for a few reasons. For anyone who wants to venture down this path,
+here are a few of the finer points you'll need to deal with:
+1. Identifying verbs is harder than it looks. For instance, take the word "vacuum." This word could be used as a noun,
+("Please hand me the vacuum.") or verb ("Please vacuum the dining room.") Vacuum is not a special word-
+in fact if you think about it, **most** verbs in the English language can be used as nouns and **most** nouns can be used as verbs.
+If you blindly convert any word that could be a verb, you'll get nonsense like "Please hand me the vacuumed."
+Therefore, in order to properly tense-alter a passage, you need to first parse the sentence to determine what words are
+are being used as verbs. You also need to parse their role in the sentence. For instance, infinitives do not change with
+tense. (We don't want e.g. "You asked me to vacuumed)".
+2. Once you have identified which word you want to change, there are so many irregular verbs and special rules, you
+really need an entire dictionary to do this properly.
+3. There are more tenses in English than you might realize. Common wisdom is that we have 3: past, present, and future.
+In fact, there are 12, and each of them has three modes: affirmative, negative, and interrogative.
+
+
+
+4. There are all sorts of cases where you would want to have multiple tenses in the same sentence, and there isn't really
+a good way to infer this automatically.
+
+
+Despite these obstacles, I managed to make a tool that works... OK. It comes with a web-app.
+Check it out on GitHub [here](https://github.com/bendichter/tenseflow).
\ No newline at end of file
diff --git a/content/posts/2018-04-23-osx-jupyter-launcher.md b/content/posts/2018-04-23-osx-jupyter-launcher.md
new file mode 100644
index 0000000..b536494
--- /dev/null
+++ b/content/posts/2018-04-23-osx-jupyter-launcher.md
@@ -0,0 +1,33 @@
+---
+title: 'OSX Jupyter Launcher'
+date: 2018-04-23
+tags:
+ - Jupyter
+ - OSX
+ - python
+---
+
+If you use Jupyter on a regular basis, the steps to launch a notebook are probably second nature, but if you take a step back, it involves a lot of prior knowledge. A few times I've tried to bring brand new eager programmers into the glorious land of Python and Jupyter, but each time I found that the whole flow was really bogged down by this preamble that is pretty technical. I'll give them an .ipynb file and then show them how to open it
+
+1. Open Terminal (What's Terminal? It looks scary.)
+2. Use `cd` to navigate to where you want.
+3. Now run this special command...
+and **finally** you are in the user-friendly land of Jupyter.
+
+Now of course all of these skills are useful, and necessary eventually, but it really bogs down the first lesson in minutia and inevitably leaves the student feeling a bit overwhelmed. There must be a better way! One solution is to set your student up with Jupyter Hub. They'll just need to click a link and they'll be up and running in no time! This is a great solution for a lot of cases, but it requires the instructor to set up a server and the student to have internet access, so this doesn't fit all cases. "Why can't I just double-click the notebook?" the student will ask (or be too embarrassed to ask). Well... um... why can't you? Now you can. Here's how.
+
+[Download me!](../../files/run_jupyter_notebook.zip) and double-click to unpack and drag to Applications or where ever you want to keep it.
+
+Navigate to a notebook in Finder, right-click and choose "Get Info", then expand "Open with:" choose "Other..." from the dropdown menu. Now navigate to and select run_jupyter_notebook. Now select "Change All..."
+
+
+
+Now you can double-click your notebooks to start them!
+
+## Caveats
+
+* This only works on Macs right now (sorry Windows. Linux users, y'all chose this life.)
+* Every time you double-click, it opens a new Terminal window.
+* This won't run a notebook in a virtual or conda environment.
+
+You can still open notebooks the normal way if you need to have more control over how the notebook is launched.
\ No newline at end of file
diff --git a/content/posts/2020-07-12-brokenaxes.md b/content/posts/2020-07-12-brokenaxes.md
new file mode 100644
index 0000000..91da6aa
--- /dev/null
+++ b/content/posts/2020-07-12-brokenaxes.md
@@ -0,0 +1,17 @@
+---
+title: 'brokenaxes'
+date: 2020-07-12
+tags:
+ - matplotlib
+ - python
+---
+
+
+
+I created a Python package for creating broken axes plots like this one:
+
+
+
+You can create discontinuities along the x and/or y axis.
+It also has compatibility for a number of other useful features like subplots and non-standard axes like log and datetime.
+Check out the documentation with plenty of examples on the [GitHub repo](https://github.com/bendichter/brokenaxes).
\ No newline at end of file
diff --git a/content/posts/2022-07-17-spiral-plot.md b/content/posts/2022-07-17-spiral-plot.md
new file mode 100644
index 0000000..0b5d4f4
--- /dev/null
+++ b/content/posts/2022-07-17-spiral-plot.md
@@ -0,0 +1,118 @@
+---
+title: 'Spiral Plot'
+date: 2022-06-17
+tags:
+- matplotlib
+- python
+---
+
+Let's use for example Google Trends results for the search term "gifts."
+Google offers this plot:
+
+
+
+It should be no surprise that these results show a cyclical trend. It looks
+like this might be an annual cycle with the max around
+Christmas time. It can be hard to create visualizations that bring out
+this cyclic pattern. Stacking years on top of each other will require
+you to break the year at a certain point, breaking continuous data and
+potentially creating the impression of two different spikes when there
+is really just one.
+
+I have created way to plot cyclic that I call a "spiral plot."
+The data starts at the center of a circle and proceeds out in a spiral.
+Each year of time forms a ring around the spiral so that a given angle
+of the circle has data from the same time of year on every loop. Here
+is the google trend for "gifts" shown as a spiral plot:
+
+
+
+This plot is more compact than the line version and may highlight some trends
+more clearly. The drawback of this approach is that earlier years are smaller
+than more recent years. You can make this less dramatic by giving the circle an
+empty center (setting `origin=-2`).
+
+
+
+Code:
+
+```python
+from typing import Optional
+
+import matplotlib.pyplot as plt
+import numpy as np
+from matplotlib.collections import PatchCollection
+from matplotlib.patches import Polygon
+
+def spiral_plot(
+ data,
+ num_cycles: int,
+ num_points_per_seg: int = 100,
+ angle: float = 0.,
+ origin: float = 0.,
+ cmap=None,
+ show_legend: bool = True,
+ ax: Optional[plt.Axes] = None
+):
+
+ if ax is None:
+ _, ax = plt.subplots(subplot_kw={'projection': 'polar'})
+
+ n_segments = len(data)
+ num_points = num_points_per_seg * n_segments
+
+ inner_rs = np.linspace(0, num_cycles, num_points)
+ outer_rs = inner_rs + 1
+ thetas = np.linspace(0, 2*np.pi*num_cycles, num_points) + angle
+
+ patches = []
+ for i in range(n_segments):
+ tt = np.hstack(
+ (
+ thetas[i*num_points_per_seg:(i+1)*num_points_per_seg],
+ thetas[i*num_points_per_seg:(i+1)*num_points_per_seg][::-1]
+ )
+ )
+ rr = np.hstack(
+ (
+ inner_rs[i*num_points_per_seg:(i+1)*num_points_per_seg],
+ outer_rs[i*num_points_per_seg:(i+1)*num_points_per_seg][::-1]
+ )
+ )
+ patch = Polygon(np.c_[tt, rr])
+ patches.append(patch)
+
+ patches = PatchCollection(patches, cmap=cmap)
+ patches.set_array(data)
+ ax.add_collection(patches)
+
+ ax.set_rlim((None, num_cycles+1))
+ ax.grid(False)
+
+ ax.set_rorigin(origin)
+
+ if show_legend:
+ ax.figure.colorbar(patches, shrink=0.6)
+
+ ax.spines.polar.set_visible(False)
+ ax.spines.inner.set_visible(False)
+
+ return ax, patches
+
+# Example usage
+import pandas as pd
+
+# data from any google trend
+fpath = "multiTimeline.csv"
+trend = "gifts"
+
+data = pd.read_csv(fpath, header=1)[f"{trend}: (United States)"].values
+
+ax, patches = spiral_plot(data, 5, angle=2*np.pi*7/12)
+
+# make it prettier
+ax.set_xticklabels(["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"])
+ax.set_xticks([2*np.pi*i/12 for i in range(0, 12)])
+ax.tick_params(axis='x', which='major', pad=-5)
+ax.tick_params(axis='y', colors='white')
+```
\ No newline at end of file
diff --git a/content/posts/2022-07-18-git-timesheet.md b/content/posts/2022-07-18-git-timesheet.md
new file mode 100644
index 0000000..a993186
--- /dev/null
+++ b/content/posts/2022-07-18-git-timesheet.md
@@ -0,0 +1,116 @@
+---
+title: 'Git Timesheet'
+date: 2022-07-18
+tags:
+- matplotlib
+- python
+---
+
+I recently faced a situation where I needed to assess the amount of work
+done by each member of a team on a project that has spanned over a year.
+That project has a git repo, and I could see when each person made a commit.
+I decided to break it down by weeks. Whenever a person submitted any commit
+to the repo on any branch, I counted them as working on the project for that week.
+Of course this is imperfect- someone could work a lot and make no commits for
+that week and someone could have submitted a commit but might have worked very
+little. Still, this seems like the most fair way to assess work I could think of.
+
+
+The code will work on any locally cloned git repo. `skip` allows you to remove
+contributors, and is ideal for handling bots. `author_map` allows you to tranform
+handles. This is ideal if members of your team make some contributions through PRs
+from a clone of the repo and some of their PRs through GitHub directly, or if
+they have multiple usernames.
+
+```python
+import os
+import pandas as pd
+import numpy as np
+import matplotlib.pyplot as plt
+import tqdm
+import datetime
+import matplotlib
+
+def git_timesheet(git_dir, skip=None, author_map=None):
+
+ if skip is None:
+ skip = [
+ "dependabot[bot]",
+ "!git for-each-ref --format='%(refname:short)' `git symbolic-ref HEAD`",
+ ]
+
+ if author_map is None:
+ author_map = dict()
+
+ os.system(f"git --git-dir {git_dir}/.git log --all --numstat --pretty=format:'--%h--%ad--%aN' --no-renames > git.log")
+
+ commits = pd.read_csv("git.log", sep="\u0012", header=None, names=['raw'])
+
+ commit_marker = commits[commits['raw'].str.startswith("--",na=False)]
+ commit_info = commit_marker['raw'].str.extract(r"^--(?P.*?)--(?P.*?)--(?P.*?)$", expand=True)
+ commit_info['date'] = pd.to_datetime(commit_info['date'])
+
+ file_stats_marker = commits[~commits.index.isin(commit_info.index)]
+ file_stats = file_stats_marker['raw'].str.split("\t", expand=True)
+ file_stats = file_stats.rename(columns={0: "insertions", 1: "deletions", 2: "filename"})
+ file_stats['insertions'] = pd.to_numeric(file_stats['insertions'], errors='coerce')
+ file_stats['deletions'] = pd.to_numeric(file_stats['deletions'], errors='coerce')
+
+ commit_data = commit_info.reindex(commits.index).fillna(method="ffill")
+ commit_data = commit_data[~commit_data.index.isin(commit_info.index)]
+ commit_data = commit_data.join(file_stats)
+
+ # get total authors and weeks
+ all_authors = commit_data["author"].unique()
+ all_authors = list(np.unique([author_map.get(x, x) for x in all_authors if x not in skip]))
+
+ dates = commit_data["date"]
+ start = dates.min()
+ stop = dates.max()
+
+ n_weeks = (stop-start).days // 7
+
+ timesheet = np.zeros((len(all_authors), n_weeks))
+
+ # iterate over commits and timesheet per week
+ for week_n in tqdm.trange(n_weeks):
+ week_start = start + datetime.timedelta(7 * (week_n-1))
+ week_stop = start + datetime.timedelta(7 * week_n)
+ commit_data_for_week = commit_data[(week_start < commit_data["date"]) & (commit_data["date"] < week_stop)]
+ authors_for_week = commit_data_for_week["author"].unique()
+ # handle different usernames
+ authors_for_week = list(np.unique([author_map.get(x, x) for x in authors_for_week]))
+ for i, author in enumerate(all_authors):
+ if author in authors_for_week:
+ timesheet[i, week_n] = 1
+
+ fig, ax = plt.subplots(figsize=(30, 10))
+ ax.imshow(timesheet, cmap="Greys")
+ ax.set_yticks(range(len(all_authors)))
+ _ = ax.set_yticklabels(all_authors)
+ ax.set_xlabel("weeks")
+
+ plt.minorticks_on()
+ plt.gca().xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(1))
+ plt.gca().yaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(1))
+ plt.grid(which="both", linewidth=0.25, color="k")
+```
+I developed a function for parsing the git log and creating a visualization
+of weeks worked by each member. The repo I used this for is private, so I will
+demonstrate it on a separate repo from CatalystNeuro that is public.
+```python
+git_timesheet(
+ "path/to/nwb-conversion-tools",
+ author_map={
+ "bendichter": "Ben Dichter",
+ "luiz": "Luiz Tauffer",
+ "luiztauffer": "Luiz Tauffer",
+ "CodyCBakerPhD": "Cody Baker",
+ "h-mayorquin": "Heberto Mayorquin",
+ "sbuergers": "Steffen Bürgers",
+ "weiglszonja": "Szonja Weigl",
+ }
+)
+```
+
+
\ No newline at end of file
diff --git a/content/posts/2022-07-18-grouped_bar.md b/content/posts/2022-07-18-grouped_bar.md
new file mode 100644
index 0000000..c3cacc9
--- /dev/null
+++ b/content/posts/2022-07-18-grouped_bar.md
@@ -0,0 +1,86 @@
+---
+title: 'Grouped Bar Plot'
+date: 2022-07-18
+tags:
+- matplotlib
+- python
+---
+
+I often have a need to plot a grouped bar plot. Matplotlib provides
+[this example](https://matplotlib.org/stable/gallery/lines_bars_and_markers/barchart.html),
+which is helpful, but not quite generalizable enough for my needs, as it only
+shows how to group 2 categories together. Here is a generalization of that
+tutorial that was very helpful for me and I hope is helpful for others as well.
+
+```python
+import matplotlib.pyplot as plt
+import numpy as np
+
+from typing import List, Optional
+
+
+def grouped_barplot(
+ data,
+ clabels: List[str],
+ xlabels: List[str],
+ gap: float = 0.3,
+ show_legend: bool = True,
+ show_bar_labels: bool = True,
+ ax: Optional[plt.Axes] = None,
+):
+ """
+
+ Parameters
+ ----------
+ data: array-like
+ size=(len(clabels), len(xlabels))
+ clabels list(str):
+ xlabels: list(str)
+ gap: float
+ Gap between categories
+ show_legend: bool
+ Show legend. Default = True
+ show_bar_labels: bool
+ Show data values above each bar. Default = True
+ ax: plt.Axes
+ If not provided, a new figure will be created.
+
+ Returns
+ -------
+ ax, all_rects
+
+ """
+
+
+ if ax is None:
+ _, ax = plt.subplots()
+
+ x = np.arange(len(xlabels)) # the label locations
+ width = (1 - gap) / len(clabels) # the width of the bars
+
+ all_rects = []
+ for i, (cdata, clabel) in enumerate(zip(data, clabels)):
+ rects = ax.bar(x - .5 + gap / 2 + i * width, cdata, width, label=clabel)
+ if show_bar_labels:
+ ax.bar_label(rects, padding=3)
+ all_rects.append(rects)
+
+ # Add some text for labels, title and custom x-axis tick labels, etc.
+ ax.set_xticks(x, xlabels)
+ if show_legend:
+ ax.legend()
+
+ return ax, all_rects
+```
+
+Example usage:
+
+```python
+grouped_barplot(
+ data=[[1,2,3,4], [2,3,4,5], [4,5,6,7]],
+ clabels=["there", "are", "categories"],
+ xlabels=["x", "labels", "go", "here"],
+)
+```
+
+
\ No newline at end of file
diff --git a/content/posts/2022-07-18-stacked-step-plot.md b/content/posts/2022-07-18-stacked-step-plot.md
new file mode 100644
index 0000000..192d86c
--- /dev/null
+++ b/content/posts/2022-07-18-stacked-step-plot.md
@@ -0,0 +1,122 @@
+---
+title: 'Stacked Step Plot'
+date: 2022-07-18
+tags:
+- matplotlib
+- python
+---
+
+Matplotlib provides a
+[stackplot](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.stackplot.html),
+which stacks area, and a [step plot](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.step.html),
+which provides steps, but there is no stacked step plot. This is useful
+when you have some accumulating resource that have different types that
+begin and end all at once. I personally had a need for this when I wanted
+to visualize my man-month commitment to my company's funded projects over
+the next 7 years.
+
+```python
+from itertools import chain
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+def plot_stacked_step(x, y, names, ax=None, sort=True):
+ x_in = x
+ y_in = y
+
+ if sort:
+ idx = np.argsort([x[0] for x in x_in])
+ x_in = np.asarray(x_in)[idx]
+ y_in = np.asarray(y_in)[idx]
+
+ if ax is None:
+ fig, ax = plt.subplots(figsize=(7, 3))
+
+ all_x = sorted(set(chain(*x_in)))
+
+ all_adj_y = []
+ for xs, ys in zip(x_in, y_in):
+ new_y = 0
+ iy = 0
+
+ adj_y = []
+ for x in all_x:
+ if x in xs:
+ new_y = ys[iy] if iy < len(ys) else 0
+ adj_y.append(new_y)
+ iy += 1
+ else:
+ adj_y.append(new_y)
+
+ all_adj_y.append(adj_y)
+
+ stacked = np.cumsum(all_adj_y, axis=0)
+
+ for name, i_stacked in list(zip(names, stacked))[::-1]:
+ ax.fill(
+ np.repeat(all_x, 2),
+ np.hstack((0, np.repeat(i_stacked, 2)))[:-1],
+ label=name,
+ )
+
+ return ax
+```
+
+Example usage (fake data)
+```python
+from datetime import datetime
+
+data = [
+ {
+ "name": "Project1",
+ "start-stop": [datetime(2021,4,1), datetime(2026, 2, 28)],
+ "man-months": 4,
+ },
+ {
+ "name": "Project2",
+ "start-stop": [datetime(2022, 1, 1), datetime(2022,12,31)],
+ "man-months": 0.5,
+ },
+ {
+ "name": "Project3",
+ "start-stop": [datetime(2019, 8, 1), datetime(2024, 4, 30)],
+ "man-months": 0.5,
+ },
+ {
+ "name": "Proejct4",
+ "start-stop": [datetime(2022, 4, 15), datetime(2023, 4, 14)],
+ "man-months": 2.,
+ },
+ {
+ "name": "Project5",
+ "start-stop": [datetime(2021, 1, 1), datetime(2024, 12, 31)],
+ "man-months": 1,
+ },
+ {
+ "name": "Projecy6",
+ "start-stop": [datetime(2021, 8, 1), datetime(2023, 7, 31)],
+ "man-months": 0.5
+ },
+ {
+ "name": "Project7",
+ "start-stop": [datetime(2022, 1, 10), datetime(2023, 9, 30)],
+ "man-months": 0.58,
+ },
+]
+
+ax = plot_stacked_step(
+ [x["start-stop"] for x in data],
+ [[x["man-months"]] for x in data],
+ [x["name"] for x in data],
+)
+
+ax.set_xlabel("time")
+ax.set_ylabel("man-months commited")
+ax.legend(bbox_to_anchor=(1, 1))
+ax.axhline(12, ls='--', color='k')
+ax.axvline(datetime.now(), ls='--', color=[.5, .5, .5])
+ax.set_ylim((0, 12.5))
+```
+
+
\ No newline at end of file
diff --git a/content/posts/2024-02-07-reachmyreps.md b/content/posts/2024-02-07-reachmyreps.md
new file mode 100644
index 0000000..fc12442
--- /dev/null
+++ b/content/posts/2024-02-07-reachmyreps.md
@@ -0,0 +1,30 @@
+---
+title: 'ReachMyReps.com: AI-Powered Civic Engagement'
+date: 2024-12-24
+tags:
+ - civic tech
+ - AI
+ - democracy
+---
+
+I'm excited to share a new tool that makes it easier than ever to engage with your elected representatives: [ReachMyReps.com](https://reachmyreps.com).
+
+
+
+## What is ReachMyReps?
+
+ReachMyReps is a simple but powerful web application that helps you connect with your representatives in government. The process is straightforward: First, you enter your address to find your representatives at all levels of government. Then you can choose from common topics or write about your own issue. Using AI, the app generates a personalized, professional letter about your chosen topic. Finally, you can review and edit the letter before sending it directly to your representatives.
+
+## Why I Built This
+
+In today's complex political landscape, it's more important than ever for citizens to make their voices heard. However, many people find it challenging to engage with their representatives. Finding accurate contact information can be time-consuming. Writing effective, professional letters requires skill and practice. And in our busy lives, finding time to engage in the democratic process often falls to the bottom of our priority list.
+
+ReachMyReps removes these barriers by automating the tedious parts while keeping you in control of the message.
+
+## How It Works
+
+The app combines several technologies to make civic engagement easier. Through address lookup, it identifies your representatives accurately. The AI-powered letter generation creates personalized, contextual content based on your input. And with direct delivery functionality, your message reaches your representatives without hassle.
+
+## Try It Out
+
+Visit [ReachMyReps.com](https://reachmyreps.com) to try it yourself. Whether you're concerned about local issues or national policy, your voice matters. ReachMyReps makes it easier than ever to be heard.
\ No newline at end of file
diff --git a/content/posts/2024-03-31-code-crafter.md b/content/posts/2024-03-31-code-crafter.md
new file mode 100644
index 0000000..f3e491e
--- /dev/null
+++ b/content/posts/2024-03-31-code-crafter.md
@@ -0,0 +1,77 @@
+---
+title: 'The new Code Crafter package for Python AST transformations'
+date: 2024-03-31
+tags:
+- python
+- code-crafter
+- ast
+- code-generation
+- code-manipulation
+---
+
+I built a new tool! [Code Crafter](https://github.com/bendichter/code-crafter) is a Python library designed for manipulating Python source code through Abstract Syntax Tree (AST) transformations. This tool simplifies the process of programmatically editing Python code, allowing developers to find and modify specific data structures such as lists, dictionaries, and sets within their code.
+
+## Basic Usage
+
+Starting with a file named `my_file.py` that contains the following code:
+
+```python
+my_list = [1, 2, 3]
+my_dict = {"key1": "value1", "key2": "value2"}
+my_set = {1, 2, 3}
+```
+
+You can use `code_crafter` to append an element to a list, add a new key-value pair to a dictionary, and add a new element to a set:
+
+```python
+
+import code_crafter as cc
+
+# Automatically apply changes to 'my_file.py'
+with cc.File("my_file.py") as file:
+ # Append an element to a list named 'my_list'
+ file.find_list("my_list").append(4)
+ # Add a new key-value pair to a dictionary named 'my_dict'
+ file.find_dict("my_dict").update(my_new_key="my_new_value")
+ # Add a new element to a set named 'my_set'
+ file.find_set("my_set").add(42)
+```
+
+After running the above code, the file `my_file.py` will be updated to:
+
+```python
+my_list = [1, 2, 3, 4]
+my_dict = {"key1": "value1", "key2": "value2", "my_new_key": "my_new_value"}
+my_set = {1, 2, 3, 42}
+```
+
+Most of the methods available for `cc.List`, `cc.Dict`, and `cc.Set` are similar to the methods available for the built-in Python data structures. For example,
+
+`cc.List` supports the following methods:
+* append
+* extend
+* insert
+* remove
+* pop
+* clear
+* reverse
+
+`cc.Dict` supports the following methods:
+* update
+* clear
+* pop
+* get
+
+`cc.Set` supports the following methods:
+* add
+* remove
+* update
+* discard
+
+These transformations also work for code that contains calls to `list()`, `dict()`, and `set()` constructors.
+
+
+## How it works
+`code_crafter` uses the `ast` module to parse Python code into an Abstract Syntax Tree (AST). The AST is then traversed to find and modify the desired data structures. Finally, the modified AST is converted back into Python code. Because the code is entirely generated from the AST, no formatting is preserved. Code can be rendered either according to the default Python AST formatting or using the `black` code formatter.
+
+This tool is useful for developers who need to programmatically edit Python code, such as when writing code generators or refactoring tools. Check it out on GitHub [here](https://github.com/bendichter/code-crafter).
\ No newline at end of file
diff --git a/content/publications/2009-10-01-paper-title-number-1.md b/content/publications/2009-10-01-paper-title-number-1.md
new file mode 100644
index 0000000..9a63926
--- /dev/null
+++ b/content/publications/2009-10-01-paper-title-number-1.md
@@ -0,0 +1,13 @@
+---
+title: "A NWB-based dataset and processing pipeline of human single-neuron activity during a declarative memory task"
+date: 2009-10-01
+venue: 'Journal 1'
+paperurl: 'http://academicpages.github.io/files/paper1.pdf'
+excerpt: 'This paper is about the number 1. The number 2 is left for future work.'
+---
+
+This paper is about the number 1. The number 2 is left for future work.
+
+[Download paper here](http://academicpages.github.io/files/paper1.pdf)
+
+Recommended citation: Your Name, You. (2009). "Paper Title Number 1." Journal 1. 1(1).
\ No newline at end of file
diff --git a/content/publications/2010-10-01-paper-title-number-2.md b/content/publications/2010-10-01-paper-title-number-2.md
new file mode 100644
index 0000000..3290e6c
--- /dev/null
+++ b/content/publications/2010-10-01-paper-title-number-2.md
@@ -0,0 +1,14 @@
+---
+title: "Paper Title Number 2"
+date: 2010-10-01
+venue: 'Journal 1'
+paperurl: 'http://academicpages.github.io/files/paper2.pdf'
+excerpt: 'This paper is about the number 2. The number 3 is left for future work.'
+citation: 'Your Name, You. (2010). "Paper Title Number 2." Journal 1. 1(2).'
+---
+
+This paper is about the number 2. The number 3 is left for future work.
+
+[Download paper here](http://academicpages.github.io/files/paper2.pdf)
+
+Recommended citation: Your Name, You. (2010). "Paper Title Number 2." Journal 1. 1(2).
\ No newline at end of file
diff --git a/content/publications/2015-10-01-paper-title-number-3.md b/content/publications/2015-10-01-paper-title-number-3.md
new file mode 100644
index 0000000..6776cd2
--- /dev/null
+++ b/content/publications/2015-10-01-paper-title-number-3.md
@@ -0,0 +1,14 @@
+---
+title: "Paper Title Number 3"
+date: 2015-10-01
+venue: 'Journal 1'
+paperurl: 'http://academicpages.github.io/files/paper3.pdf'
+excerpt: 'This paper is about the number 3. The number 4 is left for future work.'
+citation: 'Your Name, You. (2015). "Paper Title Number 3." Journal 1. 1(3).'
+---
+
+This paper is about the number 3. The number 4 is left for future work.
+
+[Download paper here](http://academicpages.github.io/files/paper3.pdf)
+
+Recommended citation: Your Name, You. (2015). "Paper Title Number 3." Journal 1. 1(3).
\ No newline at end of file
diff --git a/content/publications/_index.md b/content/publications/_index.md
new file mode 100644
index 0000000..57ef93c
--- /dev/null
+++ b/content/publications/_index.md
@@ -0,0 +1,6 @@
+---
+title: "Publications"
+layout: list
+---
+
+Research publications and academic papers.
\ No newline at end of file
diff --git a/content/talks/2018-08-10-NWB-extension-simulation.md b/content/talks/2018-08-10-NWB-extension-simulation.md
new file mode 100644
index 0000000..13b2022
--- /dev/null
+++ b/content/talks/2018-08-10-NWB-extension-simulation.md
@@ -0,0 +1,21 @@
+---
+title: "NWB extensions for storing results of large-scale neural network simulations"
+type: "Talk"
+venue: "INCF Assembly 2018"
+date: 2018-08-10
+location: "McGill University, Montreal, Canada"
+---
+
+This talk describes an NWB extension for handling the output of large-scale simulations.
+See the extension [repository on GitHub](https://github.com/catalystneuro/ndx-simulation-output) for details.
+
+
+
+
+
\ No newline at end of file
diff --git a/content/talks/2020-05-13-nwbwidgets-1.md b/content/talks/2020-05-13-nwbwidgets-1.md
new file mode 100644
index 0000000..26c25d8
--- /dev/null
+++ b/content/talks/2020-05-13-nwbwidgets-1.md
@@ -0,0 +1,20 @@
+---
+title: "NWBWidgets: Interactive visualization of NWB files"
+type: "Talk"
+venue: "NWB Remote User Days 2020"
+date: 2012-03-01
+---
+
+This video shows the lightning talk on the [NWBWidgets project](https://github.com/NeurodataWithoutBorders/nwb-jupyter-widgets) followed by the in-depth breakout session.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/content/talks/_index.md b/content/talks/_index.md
new file mode 100644
index 0000000..ea8b01c
--- /dev/null
+++ b/content/talks/_index.md
@@ -0,0 +1,6 @@
+---
+title: "Talks"
+layout: list
+---
+
+Conference talks and presentations.
\ No newline at end of file
diff --git a/content/teaching/2014-spring-teaching-1.md b/content/teaching/2014-spring-teaching-1.md
new file mode 100644
index 0000000..3c5266f
--- /dev/null
+++ b/content/teaching/2014-spring-teaching-1.md
@@ -0,0 +1,18 @@
+---
+title: "Teaching experience 1"
+type: "Undergraduate course"
+venue: "University 1, Department"
+date: 2014-01-01
+location: "City, Country"
+---
+
+This is a description of a teaching experience. You can use markdown like any other post.
+
+Heading 1
+======
+
+Heading 2
+======
+
+Heading 3
+======
\ No newline at end of file
diff --git a/content/teaching/2015-spring-teaching-2.md b/content/teaching/2015-spring-teaching-2.md
new file mode 100644
index 0000000..29f39a7
--- /dev/null
+++ b/content/teaching/2015-spring-teaching-2.md
@@ -0,0 +1,18 @@
+---
+title: "Teaching experience 2"
+type: "Workshop"
+venue: "University 1, Department"
+date: 2015-01-01
+location: "City, Country"
+---
+
+This is a description of a teaching experience. You can use markdown like any other post.
+
+Heading 1
+======
+
+Heading 2
+======
+
+Heading 3
+======
\ No newline at end of file
diff --git a/content/teaching/_index.md b/content/teaching/_index.md
new file mode 100644
index 0000000..ba0c0e1
--- /dev/null
+++ b/content/teaching/_index.md
@@ -0,0 +1,6 @@
+---
+title: "Teaching"
+layout: list
+---
+
+Teaching experience and educational activities.
\ No newline at end of file
diff --git a/hugo.toml b/hugo.toml
new file mode 100644
index 0000000..d6df6d8
--- /dev/null
+++ b/hugo.toml
@@ -0,0 +1,59 @@
+baseURL = "https://bendichter.com"
+languageCode = "en-us"
+title = "Ben Dichter"
+# theme = ""
+
+[params]
+ name = "Dr. Benjamin Dichter"
+ description = "Neuro-data scientist passionate about open data"
+ avatar = "profile.jpeg"
+ bio = "Research Software Engineer and Founder of CatalystNeuro."
+ email = "ben.dichter@catalystneuro.com"
+
+ # Social links
+ github = "bendichter"
+ twitter = "BenDichter"
+ bluesky = "bendichter.com"
+ orcid = "http://orcid.org/0000-0001-5725-6910"
+ pubmed = "https://pubmed.ncbi.nlm.nih.gov/?term=Dichter+B&cauthor_id=28269552"
+ googlescholar = "https://scholar.google.com/citations?user=_IwI_oEAAAAJ"
+ stackoverflow = "2559070/ben-dichter"
+
+[markup]
+ [markup.goldmark]
+ [markup.goldmark.renderer]
+ unsafe = true
+
+[menu]
+ [[menu.main]]
+ name = "Posts"
+ url = "/posts/"
+ weight = 10
+ [[menu.main]]
+ name = "Publications"
+ url = "/publications/"
+ weight = 20
+ [[menu.main]]
+ name = "Talks"
+ url = "/talks/"
+ weight = 30
+ [[menu.main]]
+ name = "Teaching"
+ url = "/teaching/"
+ weight = 40
+ [[menu.main]]
+ name = "Portfolio"
+ url = "/portfolio/"
+ weight = 50
+ [[menu.main]]
+ name = "CV"
+ url = "/cv/"
+ weight = 60
+
+[outputs]
+ home = ["HTML", "RSS"]
+ page = ["HTML"]
+ section = ["HTML", "RSS"]
+
+[permalinks]
+ posts = "/:title/"
\ No newline at end of file
diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html
new file mode 100644
index 0000000..7351ccd
--- /dev/null
+++ b/layouts/_default/baseof.html
@@ -0,0 +1,85 @@
+
+
+
+
+
+ {{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} | {{ .Site.Title }}{{ end }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ block "main" . }}{{ end }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/layouts/_default/list.html b/layouts/_default/list.html
new file mode 100644
index 0000000..095b6cc
--- /dev/null
+++ b/layouts/_default/list.html
@@ -0,0 +1,48 @@
+{{ define "main" }}
+
+
+
+ {{ if .Date }}
+
+ {{ end }}
+
+
+ {{ if .Summary }}
+
+ {{ .Summary }}
+ {{ if .Truncated }}
+ Read more →
+ {{ end }}
+
+ {{ end }}
+
+ {{ if .Params.tags }}
+
+ {{ range .Params.tags }}
+ {{ . }}
+ {{ end }}
+
+ {{ end }}
+
+ {{ end }}
+
+ {{ end }}
+
+{{ end }}
\ No newline at end of file
diff --git a/layouts/_default/single.html b/layouts/_default/single.html
new file mode 100644
index 0000000..1155097
--- /dev/null
+++ b/layouts/_default/single.html
@@ -0,0 +1,34 @@
+{{ define "main" }}
+
+
+
{{ .Title }}
+ {{ if .Date }}
+
+ {{ end }}
+ {{ if .Params.tags }}
+
+ {{ range .Params.tags }}
+ {{ . }}
+ {{ end }}
+
+ {{ end }}
+
+
+
+ {{ .Content }}
+
+
+ {{ if .Params.tags }}
+
+ {{ end }}
+
+{{ end }}
\ No newline at end of file
diff --git a/layouts/index.html b/layouts/index.html
new file mode 100644
index 0000000..82b0982
--- /dev/null
+++ b/layouts/index.html
@@ -0,0 +1,59 @@
+{{ define "main" }}
+