Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
253 commits
Select commit Hold shift + click to select a range
bdfcd5e
Don't spin blobs on reduced-motion devices
evowizz Mar 17, 2024
11c71cc
Update README
evowizz Mar 17, 2024
91d3dd2
Update Next.js
evowizz Mar 23, 2024
360498d
Use SVG, remove spin rotation in BlobBackground
evowizz Mar 23, 2024
294933e
Update noise vignette effect
evowizz Mar 23, 2024
05dff44
Use theme-specific noise
evowizz Mar 23, 2024
5bfa5ce
Change BlobBackground z-index
evowizz Mar 23, 2024
2a3a508
Update theme colors
evowizz Mar 23, 2024
f852f8e
Add back wordmark
evowizz Mar 23, 2024
2557d09
Add header component
evowizz Mar 23, 2024
25eae15
Add and use better noise
evowizz Mar 23, 2024
4e76428
Fix SVG (fill-rule -> fillRule)
evowizz Mar 24, 2024
7aec1e2
Add missing fontWeight values in Tailwind Config
evowizz Mar 24, 2024
bdaac1d
Update prettier config
evowizz Mar 24, 2024
94bfe6e
Add base colors in tailwind, update font feature settings
evowizz Mar 24, 2024
f9840f8
Add and use drawer
evowizz Mar 24, 2024
78b12e4
Update close button location in drawer
evowizz Mar 24, 2024
7f19fc5
Improve Drawer
evowizz Mar 24, 2024
39ca4f8
Improve motion in nav component
evowizz Mar 30, 2024
c1fde27
Add and use `oklch` utility in tailwind config
evowizz Apr 1, 2024
38c82f2
Add footer in Drawer
evowizz Apr 7, 2024
0b6a266
Update drawer size
evowizz Apr 7, 2024
8e704a2
Update nav destinations padding
evowizz Apr 7, 2024
ac7c41d
Refactor Nav component to use NavLink component
evowizz Apr 7, 2024
7cc2832
Expand top border beyond inner content
evowizz Apr 7, 2024
4722f90
Use `dvh` for better mobile support
evowizz Apr 7, 2024
5ddf774
Add 'absolute' positioning to header element
evowizz Apr 7, 2024
5d4cfc0
Add animation classes and hero component
evowizz Apr 7, 2024
c0bd87a
Reformat star-shape.tsx
evowizz Apr 7, 2024
67a8dde
Temporarily remove blob from blob background
evowizz Apr 7, 2024
8d4a321
Fix SocialLink hover
evowizz Apr 7, 2024
8daba40
Add hover effect to hero text
evowizz Apr 7, 2024
5dfede1
Update dependencies
evowizz May 23, 2024
047f165
Update light theme background
evowizz May 23, 2024
8ccb779
Fix issue when opening drawer
evowizz May 23, 2024
322ea7a
Let there be light!
evowizz May 23, 2024
e5bf61f
Update deps
evowizz Jun 26, 2024
d8f5bc0
Enable hoverOnlyWhenSupported
evowizz Jun 26, 2024
eeecdb9
Only display hover-light on supported hardware.
evowizz Jun 26, 2024
beac7eb
Update hover light
evowizz Jul 3, 2024
1a6e5d9
Add Dark Mode support
evowizz Jul 5, 2024
da879dd
Fix color transitions on dark mode toggle
evowizz Jul 5, 2024
f500ffc
Fix Social link's text color
evowizz Jul 5, 2024
31ae0d8
Make Drawer fully black
evowizz Jul 5, 2024
efe15ca
Update shadow colors
evowizz Jul 5, 2024
37a0254
Use resolvedTheme instead of theme
evowizz Jul 5, 2024
192634f
Update .eslintrc.json
evowizz Jul 5, 2024
2cb6017
Downgrade eslint
evowizz Jul 6, 2024
237f843
Make noise background fixed
evowizz Jul 6, 2024
8113011
Add TODO
evowizz Jul 6, 2024
eadb6e2
Add About section
evowizz Jul 6, 2024
5347933
Export Sun & StarShape from /svg index
evowizz Jul 6, 2024
94013c9
Add Skill grid
evowizz Jul 7, 2024
a1a9a6a
Update dependencies
evowizz Jul 13, 2024
4962837
Fix some properties in SVG logo
evowizz Jul 13, 2024
3dbcf58
Update dependencies and refactor styles for Tailwind CSS v4 compatibi…
evowizz Apr 27, 2025
f95bd6a
Refactor styles and update theme configurations
evowizz Apr 27, 2025
ec1f436
Fix light color for hero
evowizz Apr 27, 2025
2767842
Remove unused CSS layer for dark theme
evowizz Apr 28, 2025
d4f8f72
Add back dot-background & dot-canvas
evowizz Apr 28, 2025
d3355d3
Fix lint issues
evowizz Apr 28, 2025
4245b33
Add and use starburst shape
evowizz Apr 30, 2025
1139d33
Debounce canvas resizing
evowizz Apr 30, 2025
6ba17ed
Slight improvement to distance calculation
evowizz Apr 30, 2025
0759674
Draw in-between steps between mouse positions
evowizz Apr 30, 2025
2b54812
Ignore prev mouse position if it's -1, -1
evowizz Apr 30, 2025
7949c31
Fix lint issues, move debounce into utils.ts
evowizz Apr 30, 2025
8f30c20
V for Vendetta... removed the language, kept the vengeance
evowizz Apr 30, 2025
a8975ba
Add hero and projects sections with Material Design 3 theming and Nex…
evowizz Dec 22, 2025
6fb6652
Refactor styles in Drawer and ProjectGrid components for improved con…
evowizz Dec 24, 2025
eb61f41
Update Next.js
evowizz Dec 28, 2025
aba6c88
Restructure homepage layout, add project carousel & switch placeholde…
evowizz Jan 1, 2026
3031180
Migrate to ESLint flat config and update package scripts
evowizz Jan 1, 2026
87e2c3c
Update viewport height units from `dvh` to `svh`
evowizz Jan 2, 2026
77f3503
Add font variation settings tailwind utilities
evowizz Jan 2, 2026
80620e0
Configure Prettier and EditorConfig
evowizz Jan 2, 2026
744373f
Format codebase
evowizz Jan 2, 2026
d17c7bd
Reorganize files for simplicity
evowizz Jan 2, 2026
7744cfe
Re-add accidentally removed build script
evowizz Jan 2, 2026
79d74bf
Fix naming of font after various testing
evowizz Jan 2, 2026
c65f9fe
Rename font variation utility classes and update navigation link styles
evowizz Jan 2, 2026
15ca0ba
Update drawer styling
evowizz Jan 2, 2026
2ce10da
Change default theme
evowizz Jan 2, 2026
3cff773
Fix lint issues
evowizz Jan 2, 2026
0836440
Add and use Material expressive motion tokens
evowizz Jan 3, 2026
ce4e9ef
Make wordmark clickable
evowizz Jan 3, 2026
34a6173
Revert MiddleDot change in drawer
evowizz Jan 3, 2026
5990aed
Add and use Material Symbols, update header, tweak hero
evowizz Jan 4, 2026
69f9a4e
Refactor components to remove BoxBlock wrapper, adjust styles, and im…
evowizz Jan 5, 2026
6977a39
Remove remains of BoxBlock
evowizz Jan 5, 2026
0470219
Fix Carousel logic by tracking scrollability
evowizz Jan 6, 2026
c57097d
Reformat files
evowizz Jan 6, 2026
1b78173
Add "Featured in" section, reorganize logos & tweak some padding
evowizz Jan 8, 2026
23cbe22
Run formatter
evowizz Jan 8, 2026
be12a81
Implement /blog, /blog/[slug], post styling, more.
evowizz Jan 11, 2026
97ed5e2
Update Next.js & framer-motion
evowizz Jan 11, 2026
be12981
Close drawer on navigation
evowizz Jan 11, 2026
4f69c24
Update Node.js engine version to 24.x in package.json
evowizz Jan 11, 2026
4c5210f
Import back various configs from previous website
evowizz Jan 11, 2026
9476641
Add multi-theme support for rehype-pretty-code and fix incrementing v…
evowizz Jan 11, 2026
476a6ea
Refactor SVG icons: replace ArrowRight, Close, Menu, and MiddleDot wi…
evowizz Jan 12, 2026
bda09c3
Implement scrollbar stability on scroll lock
evowizz Jan 12, 2026
0b7132c
Custom selection colors
evowizz Jan 12, 2026
5ef5029
Convert the Next.js config to typescript
evowizz Jan 12, 2026
ab63a9f
Update default theme
evowizz Jan 12, 2026
8ef7f6c
Add attribution for Featured In section inspiration
evowizz Jan 12, 2026
baef275
Hide Debug panel in prod
evowizz Jan 12, 2026
324aeeb
Various icon-related tweaks
evowizz Jan 12, 2026
62e695c
Run formatter
evowizz Jan 12, 2026
2388297
Add footer component and update layout to include it
evowizz Jan 13, 2026
8f7ceef
Add Tooltip component and integrate it into MDX content
evowizz Jan 13, 2026
39cf19d
Add margin to aside elements following headings
evowizz Jan 13, 2026
a7ea020
Update dependencies
evowizz Jan 13, 2026
eb738a4
Add ThemeOverride component and simplify Material theme context API
evowizz Jan 15, 2026
dd111d6
Add support for font roundness in variation utilities
evowizz Jan 19, 2026
0d0f212
Update package.json dependencies and add back db:push script
evowizz Jan 19, 2026
7761e7b
Add writeups feature with dynamic theming and project links
evowizz Jan 19, 2026
52d7c9e
Add themeColor property to Hello World post metadata
evowizz Jan 19, 2026
970d237
Remove definition lists and details/summary sections from Hello World…
evowizz Jan 19, 2026
3bc1097
Refactor Material colors
evowizz Jan 19, 2026
22e3424
Add mobile-hover variant to tooltip visibility and adjust width settings
evowizz Jan 19, 2026
504308f
Adjust root font size for improved readability
evowizz Jan 19, 2026
07c259e
Refactor heading styles to improve typography and spacing
evowizz Jan 19, 2026
1875364
Refactor Footer component to accept className prop and adjust layout …
evowizz Jan 19, 2026
9061564
Update default theme
evowizz Jan 19, 2026
4747e8c
Add EditOnGitHub component to blog and writeup pages for easy editing…
evowizz Jan 19, 2026
ac99843
Refactor Footer component layout for improved styling and readability
evowizz Jan 19, 2026
97d9af3
Fix issue in Projects array
evowizz Jan 19, 2026
4a696c2
Add template writeup for development purposes
evowizz Jan 19, 2026
0b4712f
Rename Writeups feature to Focus
evowizz Jun 16, 2026
b558e6e
Update dependencies
evowizz Jun 16, 2026
236fc5c
Update .gitignore
evowizz Jun 16, 2026
f0cf112
Update dependencies
evowizz Jul 14, 2026
99a9ebc
Add shared design foundations
evowizz Jul 14, 2026
d1418a0
Redesign homepage and navigation
evowizz Jul 14, 2026
f8a8639
Redesign Blog pages
evowizz Jul 14, 2026
3310d66
Redesign Focus pages
evowizz Jul 14, 2026
0d33e67
Fix theme reset after article navigation
evowizz Jul 14, 2026
6b7acf4
Restyle note cards with tertiary container
evowizz Jul 16, 2026
13e7fbc
Add missing focus ring to LinkButton
evowizz Jul 16, 2026
0fd3c65
Redesign Blog and Focus index pages
evowizz Jul 16, 2026
ee2e1ca
Add Press reading experience to articles
evowizz Jul 16, 2026
90e4710
Add react-scan and html-to-image dev dependencies
evowizz Jul 17, 2026
d132ad9
Add dev bar selection tooling
evowizz Jul 17, 2026
d03f0a4
Replace debug panel with dev bar
evowizz Jul 17, 2026
db99b8b
Redesign navigation drawer
evowizz Jul 17, 2026
c9cd452
Fix dev bar overlapping page content
evowizz Jul 28, 2026
fceaebc
Replace focusRing constant with a focus-ring utility
evowizz Jul 28, 2026
a6ea313
Redesign footer
evowizz Jul 28, 2026
25aee18
Set Prettier print width to 120
evowizz Jul 28, 2026
dbe8111
Centralize reduced-motion handling
evowizz Jul 28, 2026
7fde688
Add a tools menu to the dev bar
evowizz Jul 28, 2026
64b7276
Redesign the theme debug menu
evowizz Jul 28, 2026
2e07944
Reformat the source for the 120 print width
evowizz Jul 28, 2026
5a19235
Add an interactive type specimen to the hero
evowizz Jul 29, 2026
e1ab677
Add a RowLink element and a shared Paris clock hook
evowizz Jul 31, 2026
818932e
Redesign the Work section around a picture-led Inware card
evowizz Jul 31, 2026
8a65861
Rebuild Press as a scrolling ribbon of outlet marks
evowizz Jul 31, 2026
ca063f3
Redesign the About, Skills and Contact sections
evowizz Jul 31, 2026
a4c2885
Lay the hero out as a bento and split the type specimen
evowizz Jul 31, 2026
f467456
Clean up comments
evowizz Aug 1, 2026
4d0b875
Add a scroll drift and let the About sidebar hug its content
evowizz Aug 1, 2026
a9b697d
Add spoken languages to the About facts
evowizz Aug 1, 2026
56d3cfc
Hold the scroll drift until its cell is wholly on screen
evowizz Aug 1, 2026
f02a547
Shorten the catch-all line in the Work list
evowizz Aug 1, 2026
fcbca85
Replace arbitrary values with native Tailwind v4 utilities
evowizz Aug 1, 2026
00bc89d
Rule the availability card edge to edge and align its dot to the icons
evowizz Aug 2, 2026
9f0fe18
Drop the remaining Tailwind class-string constants
evowizz Aug 2, 2026
8d70b80
Extract TextLink and SmartLink into the shared elements
evowizz Aug 2, 2026
ade91ef
Route MDX links through SmartLink, fixing hash links with no href
evowizz Aug 2, 2026
7b83ff5
Clean up comments across the rest of the codebase
evowizz Aug 2, 2026
7ccf89c
Remove the unused theme options type and an unreachable catch
evowizz Aug 2, 2026
3408156
Delete unused components, exports and the legacy scheme API
evowizz Aug 2, 2026
261ac7d
Rebuild the hero and let About cover it on scroll
evowizz Aug 7, 2026
d0d77de
Replace the footer spacer with outward corners
evowizz Aug 7, 2026
80490f8
Simplify Contact around email, location and local time
evowizz Aug 9, 2026
eaeda48
Rebuild the footer as a rounded navigation grid
evowizz Aug 9, 2026
916d915
Clarify the hero and About career copy
evowizz Aug 9, 2026
2fa2c67
Remove reader text scaling
evowizz Aug 9, 2026
a9544cd
Keep ruler endpoints visible above the footer
evowizz Aug 9, 2026
6c31b3c
Make reader ruler ticks react continuously
evowizz Aug 9, 2026
25a302e
Align article anchors with ruler destinations
evowizz Aug 9, 2026
55bb806
Replace reader settings with direct article controls
evowizz Aug 9, 2026
0669ca6
Remove the career summary from the hero
evowizz Aug 9, 2026
f097d62
Remove redundant client boundaries
evowizz Aug 9, 2026
f28ec8b
Render Press ribbon motion without client JavaScript
evowizz Aug 9, 2026
03f12ed
Move static content behind focused client islands
evowizz Aug 9, 2026
501dba0
Replace shared class strings with component variants
evowizz Aug 9, 2026
75157c4
Organize site modules by feature ownership
evowizz Aug 10, 2026
b216def
Let page surfaces reveal the footer through rounded corners
evowizz Aug 10, 2026
61cb52b
Upgrade Next.js to 16.3 and disable generated agent rules
evowizz Aug 10, 2026
eea4a21
Prefetch article shells while views resolve on request
evowizz Aug 11, 2026
afa6e55
Refresh frontend tooling and migrate to Motion
evowizz Aug 11, 2026
9a0814e
Align Node typings with the Node 24 runtime
evowizz Aug 11, 2026
e6a5177
Rename Focus to Case Studies across the site
evowizz Aug 12, 2026
3cefe31
Add a playful 404 page with an erasable address
evowizz Aug 12, 2026
8cfed92
Adapt the footer palette to each theme
evowizz Aug 12, 2026
ac4908c
Let the About copy and cards fill intermediate widths
evowizz Aug 12, 2026
3c01d10
Clamp and soften the hero scroll fade
evowizz Aug 12, 2026
2e9d9bb
Add database migrations and validate server configuration
evowizz Aug 13, 2026
297bc04
Add site metadata, feeds, and branded icons
evowizz Aug 13, 2026
8db6f7e
Add skip navigation and normalize document headings
evowizz Aug 13, 2026
06679b2
Keep development tools out of production bundles
evowizz Aug 13, 2026
d48bc51
Add responsive Inware artwork to the Work section
evowizz Aug 14, 2026
4f58504
Version Inware privacy policies and preserve the current URL
evowizz Aug 14, 2026
e6192c1
Remove the location from the navigation drawer
evowizz Aug 14, 2026
3cb049f
Publish the Inware 7 case study
evowizz Aug 24, 2026
e9237a6
Keep the case study hero at 2:1 on desktop
evowizz Aug 24, 2026
2a5a9f5
Underline article links on hover
evowizz Aug 24, 2026
e98bb0d
Frame full-bleed article media in reading mode
evowizz Aug 24, 2026
18740b4
Balance the case study overview wrap
evowizz Aug 24, 2026
2467184
Use the rainbow variant on the Inware case study
evowizz Aug 24, 2026
b1f4852
Expand the Inware case study and recapture the screenshots in green
evowizz Aug 25, 2026
57e40ab
Link the Inware case study from the Work spotlight
evowizz Aug 25, 2026
22e15af
Preload the Work lead artwork, the home LCP
evowizz Aug 25, 2026
cc4b489
Read the OG font from disk and cache it, fetch cannot load file URLs …
evowizz Aug 25, 2026
2e982d5
Use the case study hero as the Open Graph image and require it
evowizz Aug 25, 2026
cc0655e
Upgrade Next.js to 16.3.2 and take dependency patches
evowizz Aug 25, 2026
f88c935
Prerender the Open Graph cards per route and redesign them
evowizz Aug 25, 2026
17e2f24
Show the post hero in the article header
evowizz Aug 25, 2026
4a84a50
Use the tonal spot variant on the two oldest posts
evowizz Aug 25, 2026
21946da
Rewrite the site description from the About
evowizz Aug 25, 2026
f212a1f
Compose the Open Graph cards over a designed background
evowizz Aug 26, 2026
b40286c
Shorten the Material 3 Adaptive title and move Compose to its summary
evowizz Aug 26, 2026
abb2b54
Rewrite the index descriptions and render them from one source
evowizz Aug 26, 2026
5ebfdad
Exempt not-prose islands from prose link and image styling
evowizz Aug 26, 2026
b4e0649
Default the theme variant to tonal spot when a color resolves
evowizz Aug 26, 2026
29aa467
Add a Resources component for article link lists
evowizz Aug 26, 2026
855dede
Embed posts from X with react-tweet and cache them in the database
evowizz Aug 26, 2026
bb9a81a
Publish the Android haptics article
evowizz Aug 26, 2026
29a2e57
Close the haptics article with a link to the original on X
evowizz Aug 27, 2026
8ca917c
Keep tooltips inside the viewport with anchor positioning
evowizz Aug 27, 2026
0c544a3
Link the Sunbird thread from About and use the exact file count
evowizz Aug 27, 2026
efd7139
Put the Google Play link before the case study on the Inware card
evowizz Aug 27, 2026
c56d3d5
Remove the unused og API route
evowizz Aug 27, 2026
cced2d7
Restore the mcu tonal palette API
evowizz Aug 27, 2026
3bdec20
Update dependencies
evowizz Aug 27, 2026
25c31a3
Render tweet images through next/image
evowizz Aug 28, 2026
f1c7b2e
Pass the tweet image alt explicitly
evowizz Aug 28, 2026
ae247d3
Make the project image an object and drop the placeholders
evowizz Aug 28, 2026
0c058ad
Disallow all inware paths in robots
evowizz Aug 28, 2026
c351bda
Add per page canonicals, Mastodon verification, and Vercel analytics
evowizz Aug 28, 2026
7663e57
Add CI checks, refresh the README, and drop unused dependencies
evowizz Aug 28, 2026
b007393
Tighten the CSP and keep the Inware policies out of search
evowizz Aug 28, 2026
a4a84d6
Reuse the site URL for the production metadata base
evowizz Aug 28, 2026
e6a1657
Reject unknown slugs in the view counter
evowizz Aug 28, 2026
7f3a9d0
Gate autoplay videos on visibility and reduced motion
evowizz Aug 28, 2026
82e29cc
Match the MDX image sizes hint to the article measure
evowizz Aug 28, 2026
6ff6fea
Mask the press logos from static imports and add a pause control
evowizz Aug 28, 2026
4842fdb
Generate Next types in CI and bump bun
evowizz Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# http://editorconfig.org
root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
max_line_length = 100
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DRIZZLE_DATABASE_URL=YOUR_DATABASE_URL
3 changes: 0 additions & 3 deletions .eslintrc.json

This file was deleted.

26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CI

on:
pull_request:
push:
branches: [main, next]

permissions:
contents: read

jobs:
verify:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- uses: actions/checkout@v4

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.4.0

- run: bun install --frozen-lockfile
- run: bunx next typegen
- run: bun run lint
- run: bun run typecheck
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,9 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# content collections
.content-collections

# claude
.claude
8 changes: 6 additions & 2 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"printWidth": 120,
"semi": false,
"trailingComma": "all",
"singleQuote": true,
"tabWidth": 2,
"useTabs": false
}
"useTabs": false,
"plugins": [
"prettier-plugin-tailwindcss"
]
}
67 changes: 39 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,55 @@
# evowizz.dev

This repository hosts the source code for my personal website.
The source for [evowizz.dev](https://evowizz.dev), my personal website and writing archive. It is built with Next.js, React, Tailwind CSS, Content Collections, Drizzle ORM, and Neon Postgres.

## Getting Started
## Local development

To get a local copy up and running, follow these simple steps:
You need [Bun](https://bun.sh) and Node.js 24.

1. Clone the repository:
```bash
git clone https://github.com/evowizz/evowizz.dev.git
```
1. Clone the repository and enter it:

```bash
git clone https://github.com/evowizz/evowizz.dev.git
cd evowizz.dev
```

2. Install the dependencies:
```bash
bun install
```

3. Run the development server:
```bash
bun install
```

3. Create the local environment file:

```bash
cp .env.example .env.local
```

4. Set `DRIZZLE_DATABASE_URL` in `.env.local` to a Postgres connection string, then apply the schema:

```bash
bun run db:migrate
```

5. Start the development server on [localhost:3000](http://localhost:3000):

```bash
bun run dev
```

## Quality checks

Run the same checks as CI before opening a pull request:

```bash
bun dev
bun run lint
bun run typecheck
```

## Contributing

At the moment, no new features are being accepted. However, if you find a bug or have a suggestion, feel free to open an issue.
New features are not currently being accepted, but bug reports and focused fixes are welcome. Please open an issue before larger changes.

## License

```text
Copyright 2024 Dylan Roussel

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
Licensed under the [Apache License 2.0](LICENSE).
Binary file modified bun.lockb
Binary file not shown.
127 changes: 127 additions & 0 deletions content-collections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Context, defineCollection, defineConfig, Meta } from '@content-collections/core'
import { z } from 'zod'
import { compileMDX } from '@content-collections/mdx'
import rehypePrettyCode from 'rehype-pretty-code'
import rehypeSlug from 'rehype-slug'
import remarkGfm from 'remark-gfm'
import { getColor } from 'colorthief'
import path from 'path'
import { argbFromRgb, hexFromArgb, Variant } from '@evowizz/material-color-utilities-canary'

const VARIANT_MAP: Record<string, Variant> = {
monochrome: Variant.MONOCHROME,
neutral: Variant.NEUTRAL,
tonal_spot: Variant.TONAL_SPOT,
vibrant: Variant.VIBRANT,
expressive: Variant.EXPRESSIVE,
fidelity: Variant.FIDELITY,
content: Variant.CONTENT,
rainbow: Variant.RAINBOW,
fruit_salad: Variant.FRUIT_SALAD,
}

type VariantName = keyof typeof VARIANT_MAP
const VARIANT_NAMES = Object.keys(VARIANT_MAP) as [VariantName, ...VariantName[]]

type TransformInput = {
_meta: Meta
content: string
themeColor?: string
themeVariant?: VariantName
image?: string
}

async function transform<T extends TransformInput>(document: T, context: Context) {
const mdx = await compileMDX(context, document, {
remarkPlugins: [remarkGfm],
rehypePlugins: [
rehypeSlug,
[
rehypePrettyCode,
{
theme: {
light: 'github-light',
dark: 'dark-plus',
},
keepBackground: false,
},
],
],
})

let themeColor = document.themeColor
if (!themeColor && document.image) {
themeColor = (await extractColorFromImage(document.image)) ?? undefined
}

// Without a color, ThemeOverride keeps the site theme, so the variant stays unset too.
let themeVariant = document.themeVariant ? VARIANT_MAP[document.themeVariant] : undefined
if (themeVariant === undefined && themeColor) {
themeVariant = Variant.TONAL_SPOT
}

return {
...document,
slug: document._meta.path,
themeColor,
themeVariant,
mdx,
}
}

const posts = defineCollection({
name: 'posts',
directory: 'content/posts',
include: '**/*.mdx',
schema: z.object({
title: z.string(),
publishedAt: z.string(),
summary: z.string(),
content: z.string(),
image: z.string().optional(),
themeColor: z.string().optional(),
themeVariant: z.enum(VARIANT_NAMES).optional(),
hidden: z.boolean().default(false),
}),
transform,
})

const caseStudies = defineCollection({
name: 'caseStudies',
directory: 'content/case-studies',
include: '**/*.mdx',
schema: z.object({
title: z.string(),
overview: z.string(),
stack: z.array(z.string()),
role: z.string().optional(),
image: z.string(),
themeColor: z.string().optional(),
themeVariant: z.enum(VARIANT_NAMES).optional(),
content: z.string(),
hidden: z.boolean().default(false),
}),
transform,
})

export default defineConfig({
content: [posts, caseStudies],
})

async function extractColorFromImage(imagePath: string): Promise<string | null> {
if (imagePath.startsWith('http') || imagePath.startsWith('/api/')) {
return null
}

try {
const fullPath = path.join(process.cwd(), 'public', imagePath)
// colorthief v3 quantizes in OKLCH by default, so pin to rgb to match prior output
const color = await getColor(fullPath, { colorSpace: 'rgb' })
if (!color) return null
const { r, g, b } = color.rgb()
return hexFromArgb(argbFromRgb(r, g, b))
} catch (error) {
console.warn(`Failed to extract color from ${imagePath}:`, error)
return null
}
}
89 changes: 89 additions & 0 deletions content/case-studies/inware.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
title: 'Inware 7'
overview: 'Designing a clearer way to explore everything Android can tell you about a device.'
stack: ['Material 3', 'Kotlin', 'Jetpack Compose', 'Navigation 3']
role: 'Design and development'
image: '/content/case-studies/inware/hero.png'
themeColor: '#33CC7A'
themeVariant: 'rainbow'
---

Inware 7 is a complete overhaul of my Android device information app. It shipped in April 2026 and has kept moving since. It's the completion of the <Tooltip content="Android's modern toolkit for building interfaces entirely in Kotlin.">Jetpack Compose</Tooltip> transition, and a complete <Tooltip content="The latest, more expressive evolution of Google's Material Design language.">Material 3 Expressive</Tooltip> redesign. I had no specific design in mind, only certain constraints. Every decision was implemented and tested live on a device so the motion could be judged for real, whether by me or by testers. That is how I made sure Inware felt right.

## Jetpack Compose

I started fiddling with Jetpack Compose in 2019, and tracked its progress ever since. In 2020, before Compose was even stable, I published [compose-to-edge](https://github.com/evowizz/compose-to-edge), a small library for going edge-to-edge in Compose apps. In 2022, I started implementing Compose in Inware: I knew Google was betting on it. It was showing up in Android itself, in Settings and SystemUI, and Google had built Now in Android, a reference app written with it. So while Android Views were still used at the time, Compose was a modern toolkit that was actively being used and developed. By the last 6.x release, various components of Inware were already Compose, but it was still a hybrid app.

Inware 7 is the first version fully written in Compose, a decision Google confirmed earlier in 2026, when [Android UI officially went Compose first](https://android-developers.googleblog.com/2026/05/android-ui-development-is-compose-first.html).

## From 6 to 7

For years, Inware relied on Overpass Mono as its brand font. The font, along with the cards of the home screen, made Inware easy to recognize up to version 6. With Inware 7, things changed. While the brand color remains `#33CC7A` and <Tooltip content="An Android feature that recolors apps with a palette taken from the wallpaper or picked by the user.">dynamic colors</Tooltip> are still supported, Google Sans Flex now replaces Overpass Mono. It is a variable font with many axes to tune, it is visually balanced, and it is the font Pixel devices use, so it feels at home on Android. Users who would rather not have it can switch to the system font in the settings. The cards are also gone. The refresh went all the way down to the app icon and the splash screen.

![The Inware 6 and Inware 7 app icons side by side](/content/case-studies/inware/inware-logos.png 'The Inware 6 icon on the left, the Inware 7 icon on the right.')

Inware is represented by a chip as its icon, and the new logo introduced with version 7 keeps it. The old logo was a good one, but it felt too generic, too simple. And as the app evolves, so should the logo. We're entering an era where modern logos use gradients: Android app icons are getting redesigned all over the place, and as Inware is built to fit Android, its logo should follow. The changes remain small: a grid as the background, smoothed corners, tweaked colors, etc. So Inware remains easy to identify, but feels modern.

Inware is made to embrace Android: it follows Material closely, and it should feel like it belongs on the device it describes. This is why dynamic colors are enabled by default. Like the system, the app can take the colors of the wallpaper. Dynamic colors are only as good as the device's implementation, though. On a Pixel, they behave the way Material intends. On Samsung's One UI, they are more of an adventure. Fixing that is on Samsung, not on apps, so Inware simply lets users turn dynamic colors off.

![The Inware 6 and Inware 7 home screens side by side](/content/case-studies/inware/home.png 'Inware 6 on the left, Inware 7 on the right')

The home screen of Inware 6 was a grid of large cards, each showing an icon and a name. The cards did not describe the pages behind them, so the only way to know what a page contained was to open it.

In Inware 7, every destination is still there, but each one now has a description. And above the list, a dashboard shows a few live values, through tiles that users can pick and arrange themselves.

## Navigation

![The floating toolbar from the Inware 7 beta](/content/case-studies/inware/toolbar.png 'The floating toolbar, as it appeared in the beta.')

The home screen did not start out as a single screen. Initially, the dashboard, the explore page with the list of destinations, and the settings were three separate pages. The plan was to move between them with a Navigation Drawer, but I switched away from it before it was ever released: in a drawer, the dashboard would have been yet another page in a list, sitting somewhere between Device and Hardware. A floating toolbar at the bottom took its place. It worked well.

A dedicated dashboard page had room to preview a lot more tiles as soon as the app opened. But testers highlighted how it became harder for them to quickly jump to a specific data page.

One way to do that would have been to make the explore page the default, or to make the default page configurable. But in both cases, the dashboard page would have become less useful. Instead, I merged both pages into a single screen, and one of my favorite components made it possible: the Backdrop.

The dashboard became a back layer of live data, the destinations became a front layer that you can drag over it, and the settings moved into a button at the top. The Backdrop was released in beta, and people quickly preferred it to the toolbar.

## Dashboard

![A close-up of the Inware 7 tiles](/content/case-studies/inware/tiles.png 'Four tiles on the first page of the dashboard.')

The dashboard is made of tiles. Each of its pages contains four of them. When four tiles are not enough, additional pages are just a swipe away. Every tile has the same size, which allows the grid to adapt to any screen without the tiles having to change. However, not everyone cares about the same data, which is why users can also customize their dashboard.

![The edit dashboard, with the tile catalog below the grid](/content/case-studies/inware/edit.png 'The edit screen, with the catalog of unused tiles below the grid.')

In edit mode, users can reorder tiles, add them, or remove them. Tiles that are not in use sit in a catalog below the grid, grouped by destination. You can't really break anything while editing. An undo button shows up after your first change and reverts changes one by one, and the reset button stays disabled until the dashboard no longer matches the default.

The editing system was also made to look like the editor of Android's own <Tooltip content="The tiles in Android's notification shade, like Wi-Fi, Bluetooth, or the flashlight.">Quick Settings</Tooltip> panel. Inware is built to stay close to how the system works, and this was a way to avoid asking users to learn a new system: they can edit their dashboard the same way they already edit their QS Tiles.

## Inside a page

![A tile and the page it opens](/content/case-studies/inware/tile-and-page.png 'The battery tile on the dashboard, and the page it opens.')

Each tile is both a preview and an entry point. The battery tile only shows the level. The page it opens has the rest: health, cycle count, charge current, temperature, and whatever else the device can report.

I also made every destination declare its parent. Back navigation and `iw://` deep links both rely on that declaration, so a page opened from a link behaves exactly like a page opened by hand.

![A long press on a value opens copy and share](/content/case-studies/inware/long-press.png 'The copy and share menu, here on the battery cycle count.')

The values are the reason people open Inware, so I made them easy to take out of it. A long press on any value opens a small menu to copy or share it, without going through a screenshot. It may sound like a detail, but values like a kernel version or a build number are long, and nobody should have to retype them by hand.

Material 3 places menus relative to the component that opened them, and prefers extending to the right, which for a long press often puts the menu under the thumb of a right handed user. Inware handles the menu position differently. I believe a touch context menu should open away from the hand, so Inware uses a custom position provider that opens the menu on the opposite side of the press. You usually press on the side your thumb rests on, so a long press on the right side of the app opens the menu toward the left, and a long press on the left opens it toward the right. I [shared that view](https://issuetracker.google.com/issues/499330952) with the Material team.

## Bottom sheets

![A confirmation presented as a bottom sheet](/content/case-studies/inware/bottom-sheet.png 'A confirmation as a bottom sheet, here for enabling developer options.')

A confirmation like this is usually a dialog: a card in the middle of the screen, two small buttons in a row. On a tall phone, that puts the decision away from the thumb, and it leaves little room for the warning itself.

Inware uses a bottom sheet instead, which is what the Material 3 guidelines suggest: on mobile, a modal bottom sheet [is recommended](https://m3.material.io/components/bottom-sheets/guidelines#1cb775b6-6d2b-4d50-96ad-1862727e986b) as an alternative to simple dialogs and inline menus, especially when actions need icons or longer descriptions. The page stays visible behind a scrim, the sheet sits where the hand already is, and Proceed and Cancel are full-width rows rather than dialog buttons. This one cannot be dismissed by tapping outside or by going back, and since it cannot be swiped away either, it does not show a drag handle. Enabling developer options is a real choice, so the sheet waits for one.

The same sheet is used for confirmations and for lists of actions, so a warning does not look like a different kind of UI from the rest of the app.

## Big screens (in beta)

![The split layout on an unfolded foldable](/content/case-studies/inware/split.png 'The split layout on an unfolded foldable. The divider follows the hinge.')

On large screens, Inware uses a <Tooltip content="A layout that shows a list next to the page of the selected item.">list detail</Tooltip> layout: the destinations stay on the left, and the selected page opens next to them. The list could not simply transform into a Navigation Drawer or a Navigation Rail, because the selected page would then be alone on the screen, either quite large or centered with empty space around it, and the dashboard would have had nowhere to sit. Material 3 provides a scaffold for this layout, and while it animates the transition between the list and a page, it does not animate the move from one page to the next, which in Inware is most of the movement. So I wrapped the Material 3 scaffold into my own and added the missing transition. It ships in 7.1.0.

Inware 7 did not stop at 7.0. It is still built the same way: every change is tested live on a device, then sent to the testers. The Backdrop came out of that, so did the dashboard and its editor, and so will the list detail layout. That's Inware for you.
20 changes: 20 additions & 0 deletions content/case-studies/template.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: 'Template'
overview: 'This is a template case study for development purposes.'
stack: ['Template']
role: 'Template Role'
image: '/api/placeholder/1200/600'
hidden: true
---

This is a placeholder case study used during development.

![A wide placeholder screenshot](/api/placeholder/1600/900 'A landscape placeholder, standing in for a screenshot.')

Sections further down get their own figures too, so spacing and caption alignment can be checked against real content.

![A portrait placeholder screenshot](/api/placeholder/1200/1500 'A portrait placeholder, for tall screens or detail shots.')

Full-bleed figures should escape the container to the viewport edges, while this text and the captions above stay inside the column.

![A short, wide placeholder screenshot](/api/placeholder/1600/700 'A short, wide placeholder, for a workflow overview.')
Loading
Loading