Skip to content

Commit 9944dfb

Browse files
committed
design: white stock, a true red, and hyperref's palette on tags
The cream ground and the burgundy sat close enough in value that the page read soft. Take the stock to near-white (#FCFCFA), the ink to #1A1A1A, and the accent from burgundy to an actual red (#C5221F, 5.8:1) so links carry the way coloured links carry in a paper. Introduce three more accents named after hyperref's own link roles — url blue, cite green, note amber, each over 4.5:1 on the new stock. Tag chips cycle through all four and the citation markers in Publications set in mono and in the cite colour, the way \cite renders under colorlinks. That is where the green, blue, and yellow live; the red stays the site's voice. Colours cycle with a chip's position, not a hash of its name. Hashing was the first attempt and it clumped — one post came out green, blue, green, green. Position guarantees an even run, and since tags are listed in the same order everywhere, a tag still keeps its colour across views. The share card, the favicon's inner diamond, the theme-color meta, and the manifest follow the same palette. Claude-Session: https://claude.ai/code/session_01S3mVZMgbynFxc2N874K9nH
1 parent 6528384 commit 9944dfb

10 files changed

Lines changed: 144 additions & 62 deletions

File tree

public/favicon.svg

Lines changed: 2 additions & 2 deletions
Loading

public/site.webmanifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"description": "Portfolio of Diego Said Anaya Mancilla — software engineer building production backend and full-stack systems.",
55
"start_url": "/",
66
"display": "standalone",
7-
"background_color": "#FAF8F3",
7+
"background_color": "#FCFCFA",
88
"theme_color": "#1A1A1A",
99
"icons": [
1010
{

src/layouts/BaseLayout.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const fontHref =
4545
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
4646
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
4747
<link rel="manifest" href="/site.webmanifest" />
48-
<meta name="theme-color" content="#FAF8F3" />
48+
<meta name="theme-color" content="#FCFCFA" />
4949
<link rel="sitemap" href="/sitemap-index.xml" />
5050

5151
{/* Open Graph */}

src/layouts/BlogPost.astro

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
---
22
import BaseLayout from './BaseLayout.astro';
33
import { monthYearUpper, readTimeUpper } from '@/lib/format';
4+
import { tagClass } from '@/lib/tags';
45
import 'katex/dist/katex.min.css';
56
67
interface Props {
@@ -37,8 +38,8 @@ const { title, subtitle, excerpt, date, readMinutes, tags, slug } = Astro.props;
3738
<p class="text-[17px] text-muted mt-3 italic leading-relaxed">{subtitle}</p>
3839
<div class="flex flex-wrap gap-2 mt-4">
3940
{
40-
tags.map((tag) => (
41-
<span class="text-[12px] font-mono text-muted border border-rule px-2 py-0.5 rounded">
41+
tags.map((tag, i) => (
42+
<span class={`text-[12px] font-mono border px-2 py-0.5 rounded ${tagClass(i)}`}>
4243
{tag}
4344
</span>
4445
))

src/lib/tags.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Tag colours.
3+
*
4+
* The colour cycles with the tag's position in its list rather than with the
5+
* tag's name. Hashing the name was the first attempt and it clumped: one post
6+
* came out green, blue, green, green. Cycling by position guarantees an even
7+
* spread and never puts two of the same colour side by side, and because tags
8+
* are listed in the same order on every view, a given tag keeps its colour
9+
* wherever it appears.
10+
*
11+
* The two lists are the same four colours in the same order: Tailwind classes
12+
* for the pages, hex for the Open Graph card, which Satori renders without ever
13+
* seeing the stylesheet. Keep them in step.
14+
*/
15+
16+
/** Written out in full so Tailwind's scanner finds each class literally. */
17+
const TAG_CLASSES = [
18+
'text-accent border-accent/30 bg-accent/5',
19+
'text-url border-url/30 bg-url/5',
20+
'text-cite border-cite/30 bg-cite/5',
21+
'text-note border-note/30 bg-note/5',
22+
];
23+
24+
const TAG_HEXES = ['#C5221F', '#185ABC', '#137333', '#B45309'];
25+
26+
export function tagClass(index: number) {
27+
return TAG_CLASSES[index % TAG_CLASSES.length];
28+
}
29+
30+
export function tagHex(index: number) {
31+
return TAG_HEXES[index % TAG_HEXES.length];
32+
}

src/pages/blog/index.astro

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { getCollection } from 'astro:content';
33
import BaseLayout from '@/layouts/BaseLayout.astro';
44
import { monthYear, readTime } from '@/lib/format';
5+
import { tagClass } from '@/lib/tags';
56
67
// Ascending by date, matching the order the hand-maintained POSTS array used.
78
const posts = (await getCollection('blog')).sort(
@@ -33,8 +34,8 @@ const posts = (await getCollection('blog')).sort(
3334
</h2>
3435
<p class="text-[15px] text-muted mt-2 leading-relaxed">{post.data.excerpt}</p>
3536
<div class="flex flex-wrap gap-2 mt-3">
36-
{post.data.tags.map((tag) => (
37-
<span class="text-[12px] font-mono text-muted border border-rule px-2 py-0.5 rounded">
37+
{post.data.tags.map((tag, i) => (
38+
<span class={`text-[12px] font-mono border px-2 py-0.5 rounded ${tagClass(i)}`}>
3839
{tag}
3940
</span>
4041
))}

src/pages/index.astro

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,27 @@ const SKILLS = [
109109
},
110110
];
111111
112+
// The reference marker is split out so it can set in mono and in the citation
113+
// colour, the way a paper's \cite renders under hyperref.
112114
const PUBLICATIONS = [
113-
'[1] D.S. Anaya Mancilla et al. "VCF/Plotein: visualization and prioritization of genomic variants from VCF files." <em>Bioinformatics</em>, Oxford Academic, 2019.',
114-
'[2] D.S. Anaya Mancilla et al. "ADR-47: The Case Against PACT xUSD Launch." xBacked DAO, 2022.',
115-
'[3] D.S. Anaya Mancilla et al. "ADR-46: Vault Looping." xBacked DAO, 2022.',
116-
'[4] D.S. Anaya Mancilla et al. "xBacked Litepaper v2.0." xBacked DAO, 2022.',
115+
{
116+
ref: '[1]',
117+
citation:
118+
'D.S. Anaya Mancilla et al. "VCF/Plotein: visualization and prioritization of genomic variants from VCF files." <em>Bioinformatics</em>, Oxford Academic, 2019.',
119+
},
120+
{
121+
ref: '[2]',
122+
citation:
123+
'D.S. Anaya Mancilla et al. "ADR-47: The Case Against PACT xUSD Launch." xBacked DAO, 2022.',
124+
},
125+
{
126+
ref: '[3]',
127+
citation: 'D.S. Anaya Mancilla et al. "ADR-46: Vault Looping." xBacked DAO, 2022.',
128+
},
129+
{
130+
ref: '[4]',
131+
citation: 'D.S. Anaya Mancilla et al. "xBacked Litepaper v2.0." xBacked DAO, 2022.',
132+
},
117133
];
118134
119135
const HONORS = [
@@ -231,7 +247,10 @@ const HONORS = [
231247
<h2 class="text-[22px] font-bold font-serif mb-6">Publications</h2>
232248
{
233249
PUBLICATIONS.map((entry) => (
234-
<p class="text-[15px] leading-[1.7] mb-4 font-serif text-ink" set:html={entry} />
250+
<p class="text-[15px] leading-[1.7] mb-4 font-serif text-ink flex gap-2">
251+
<span class="font-mono text-[13px] text-cite shrink-0 pt-0.5">{entry.ref}</span>
252+
<span set:html={entry.citation} />
253+
</p>
235254
))
236255
}
237256
</section>

src/pages/og/[...route].png.ts

Lines changed: 54 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { readFileSync } from 'node:fs';
44
import { join } from 'node:path';
55
import satori from 'satori';
66
import { Resvg } from '@resvg/resvg-js';
7+
import { tagHex } from '@/lib/tags';
78

89
/**
910
* Build-time Open Graph images, one per route.
@@ -13,17 +14,18 @@ import { Resvg } from '@resvg/resvg-js';
1314
* Rendering here means each post gets a preview carrying its own title instead
1415
* of a single generic card.
1516
*
16-
* The card is the site's own palette and type: cream stock, serif title, mono
17-
* metadata. The burgundy colophon band across the foot is what carries it in a
18-
* feed — an all-cream card at thumbnail size reads as a blank rectangle.
17+
* The card is the site's own palette and type: near-white stock, serif title,
18+
* mono metadata, tags in the same four colours they wear on the site. The red
19+
* colophon band across the foot is what carries it in a feed — an all-white
20+
* card at thumbnail size reads as a blank rectangle.
1921
*/
2022

21-
const PAPER = '#FAF8F3';
22-
const INK = '#1F1D1A';
23-
const ACCENT = '#7B2D26';
24-
const MUTED = '#6B665E';
25-
/** Cream dimmed against the burgundy band, the counterpart of MUTED on paper. */
26-
const BAND_MUTED = '#DFC8C4';
23+
const PAPER = '#FCFCFA';
24+
const INK = '#1A1A1A';
25+
const ACCENT = '#C5221F';
26+
const MUTED = '#6B6B70';
27+
/** White dimmed against the red band, the counterpart of MUTED on paper. */
28+
const BAND_MUTED = '#F3C9C8';
2729

2830
// Read from the project root, not import.meta.url: this module is bundled into
2931
// dist/.prerender during the build, so a relative URL would resolve there. The
@@ -41,8 +43,10 @@ const AUTHOR = 'Diego Said Anaya Mancilla';
4143
interface Card {
4244
title: string;
4345
subtitle: string;
44-
/** Mono kicker above the title. Omitted where the title already says it. */
45-
eyebrow?: string;
46+
/** Plain mono kicker, for cards that have no tags to show. */
47+
kicker?: string;
48+
/** Post tags, each in the colour it carries on the site. */
49+
tags?: string[];
4650
/**
4751
* Left half of the colophon band. Omitted on the home card, whose title is
4852
* already the name — the band would only repeat it.
@@ -57,7 +61,7 @@ export async function getStaticPaths() {
5761
{
5862
route: 'index',
5963
card: {
60-
eyebrow: 'Software Engineer',
64+
kicker: 'Software Engineer',
6165
title: AUTHOR,
6266
subtitle:
6367
'Production backend and full-stack systems. Portfolio, publications, and technical writing.',
@@ -75,7 +79,7 @@ export async function getStaticPaths() {
7579
...posts.map((post) => ({
7680
route: `blog/${post.id}`,
7781
card: {
78-
eyebrow: post.data.tags.slice(0, 3).join(' · '),
82+
tags: post.data.tags.slice(0, 3),
7983
title: post.data.title,
8084
subtitle: post.data.subtitle,
8185
byline: AUTHOR,
@@ -114,7 +118,42 @@ function titleSize(title: string) {
114118
return 80;
115119
}
116120

117-
function template({ title, subtitle, eyebrow, byline }: Card) {
121+
const eyebrowText = {
122+
fontFamily: MONO,
123+
fontSize: '21px',
124+
letterSpacing: '0.14em',
125+
textTransform: 'uppercase',
126+
};
127+
128+
/** Tags keep their own colours; a plain kicker takes the accent. */
129+
function eyebrow({ kicker, tags }: Card) {
130+
const row = (children: unknown[]) =>
131+
h('div', { style: { display: 'flex', marginBottom: '30px' } }, ...children);
132+
133+
if (tags?.length) {
134+
return row(
135+
tags.flatMap((tag, i) => [
136+
i > 0
137+
? h(
138+
'div',
139+
{ style: { display: 'flex', ...eyebrowText, color: MUTED, padding: '0 14px' } },
140+
'·',
141+
)
142+
: null,
143+
h('div', { style: { display: 'flex', ...eyebrowText, color: tagHex(i) } }, tag),
144+
]),
145+
);
146+
}
147+
148+
if (kicker) {
149+
return row([h('div', { style: { display: 'flex', ...eyebrowText, color: ACCENT } }, kicker)]);
150+
}
151+
152+
return null;
153+
}
154+
155+
function template(card: Card) {
156+
const { title, subtitle, byline } = card;
118157
return h(
119158
'div',
120159
{
@@ -145,23 +184,7 @@ function template({ title, subtitle, eyebrow, byline }: Card) {
145184
padding: '0 84px',
146185
},
147186
},
148-
eyebrow
149-
? h(
150-
'div',
151-
{
152-
style: {
153-
display: 'flex',
154-
fontFamily: MONO,
155-
fontSize: '21px',
156-
color: ACCENT,
157-
letterSpacing: '0.14em',
158-
textTransform: 'uppercase',
159-
marginBottom: '30px',
160-
},
161-
},
162-
eyebrow,
163-
)
164-
: null,
187+
eyebrow(card),
165188
h(
166189
'div',
167190
{

src/styles/global.css

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@
1010

1111
body {
1212
font-family: 'Source Serif 4', Georgia, 'Times New Roman', serif;
13-
background-color: #FAF8F3;
14-
color: #1F1D1A;
13+
background-color: #FCFCFA;
14+
color: #1A1A1A;
1515
-webkit-font-smoothing: antialiased;
1616
-moz-osx-font-smoothing: grayscale;
1717
}
1818

1919
:focus-visible {
20-
outline: 2px solid #7B2D26;
20+
outline: 2px solid #C5221F;
2121
outline-offset: 2px;
2222
}
2323

2424
::selection {
25-
background-color: rgba(123, 45, 38, 0.18);
25+
background-color: rgba(197, 34, 31, 0.15);
2626
}
2727
}
2828

@@ -53,11 +53,11 @@
5353
/* Shiki renders <pre class="astro-code"> with an inline background from the
5454
theme; override it to the paper-toned block the site used previously. */
5555
.blog-article pre {
56-
background-color: #F3F0E9 !important;
57-
color: #1F1D1A;
56+
background-color: #F6F6F3 !important;
57+
color: #1A1A1A;
5858
padding: 1.25rem 1.5rem;
5959
border-radius: 0.5rem;
60-
border: 1px solid #E3DED4;
60+
border: 1px solid #E4E4E1;
6161
font-family: 'IBM Plex Mono', monospace;
6262
font-size: 14px;
6363
line-height: 1.7;
@@ -69,7 +69,7 @@
6969
.blog-article code {
7070
font-family: 'IBM Plex Mono', monospace;
7171
font-size: 0.875em;
72-
background-color: #F3F0E9;
72+
background-color: #F2F2EF;
7373
padding: 0.125rem 0.375rem;
7474
border-radius: 0.25rem;
7575
}
@@ -105,13 +105,13 @@
105105
}
106106

107107
.blog-article a {
108-
color: #7B2D26;
108+
color: #C5221F;
109109
text-decoration: underline;
110110
text-underline-offset: 4px;
111111
}
112112

113113
.blog-article a:hover {
114-
color: #1F1D1A;
114+
color: #1A1A1A;
115115
}
116116

117117
/* KaTeX display blocks scroll rather than overflow the measure. */
@@ -132,12 +132,12 @@
132132
.blog-article th {
133133
font-weight: 600;
134134
text-align: left;
135-
border-top: 1.5px solid #1F1D1A;
136-
border-bottom: 1px solid #1F1D1A;
135+
border-top: 1.5px solid #1A1A1A;
136+
border-bottom: 1px solid #1A1A1A;
137137
}
138138

139139
.blog-article tr:last-child td {
140-
border-bottom: 1.5px solid #1F1D1A;
140+
border-bottom: 1.5px solid #1A1A1A;
141141
}
142142

143143
.blog-article th[align='right'],

tailwind.config.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@ module.exports = {
44
theme: {
55
extend: {
66
colors: {
7-
paper: '#FAF8F3',
8-
ink: '#1F1D1A',
9-
accent: '#7B2D26',
10-
muted: '#6B665E',
11-
rule: '#E3DED4',
7+
paper: '#FCFCFA',
8+
ink: '#1A1A1A',
9+
muted: '#6B6B70',
10+
rule: '#E4E4E1',
11+
// Named after hyperref's own link roles. Accent is the site's voice;
12+
// the other three appear on tag chips and citation markers. Every one
13+
// clears 4.5:1 on paper.
14+
accent: '#C5221F',
15+
url: '#185ABC',
16+
cite: '#137333',
17+
note: '#B45309',
1218
},
1319
fontFamily: {
1420
serif: ['"Source Serif 4"', 'Georgia', '"Times New Roman"', 'serif'],

0 commit comments

Comments
 (0)