Refine Swiss + Editorial themes; add optional date field
Swiss: drop all-caps on titles, soften heavy ink borders to muted rules or remove them where background contrast carries the form, balance hero name weights (smaller lighter first name vs heavier italic last name), fix unreadable white description text on portfolio hover. Editorial: enlarge the section "No" mark and set it in italic Fraunces so it reads as typography instead of a tick, swap the first name into DM Serif Display for a different J, sharpen the section number caption. Client tile: contain custom logos at 86% so wide marks like "The Cooperative Way" stop getting cropped to "operative". New: optional `date` field on every item type (link/card/portfolio/ youtube/client). Accepts YYYY, YYYY-MM, or YYYY-MM-DD. Rendered human-friendly with the raw ISO preserved in <time datetime>. Documented in README and demonstrated in links.example.json.
This commit is contained in:
31
README.md
31
README.md
@@ -114,6 +114,37 @@ has `type: "client"`.
|
|||||||
Every item lives in a section's `items` array and has a `type` field. The
|
Every item lives in a section's `items` array and has a `type` field. The
|
||||||
five available types are documented below.
|
five available types are documented below.
|
||||||
|
|
||||||
|
#### Optional `date` (any item type)
|
||||||
|
|
||||||
|
Every item type supports an optional `date` string. Use it to surface
|
||||||
|
"published", "shipped", "released", or "worked on" timestamps without
|
||||||
|
changing the item's `type`.
|
||||||
|
|
||||||
|
Accepted formats (all ISO-style, all interpreted as plain strings — no
|
||||||
|
time zones, no parsing surprises):
|
||||||
|
|
||||||
|
| Input | Rendered as | Use for |
|
||||||
|
|----------------|------------------------|------------------------------------|
|
||||||
|
| `"2026"` | `2026` | Year-only — "this is from 2026" |
|
||||||
|
| `"2026-05"` | `May 2026` | Year + month — soft / recurring |
|
||||||
|
| `"2026-05-14"` | `May 14, 2026` | Full date — talks, launches, posts |
|
||||||
|
|
||||||
|
Where it appears per type:
|
||||||
|
|
||||||
|
- **`link`** — small monospaced caption under the host (separated by a `·`).
|
||||||
|
- **`card` (project)** — at the end of the tag row.
|
||||||
|
- **`portfolio`** — under the caption description.
|
||||||
|
- **`youtube`** — inline after the title overlay.
|
||||||
|
- **`client`** — rendered as a small caption under the client title.
|
||||||
|
|
||||||
|
The raw value is also written to the `<time datetime="…">` attribute so
|
||||||
|
the original ISO string is preserved for machines and screen readers,
|
||||||
|
even though humans see the friendly form.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "type": "card", "title": "CoVote launched", "date": "2025-11-04" }
|
||||||
|
```
|
||||||
|
|
||||||
#### `type: "link"` — standard link card
|
#### `type: "link"` — standard link card
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -125,6 +125,12 @@ body::before {
|
|||||||
letter-spacing: -0.045em;
|
letter-spacing: -0.045em;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
.hero__name .hero__name-first {
|
||||||
|
font-family: "DM Serif Display", var(--display);
|
||||||
|
font-weight: 400;
|
||||||
|
font-variation-settings: normal;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
.hero__name em {
|
.hero__name em {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-variation-settings: "opsz" 144, "SOFT" 100, "WONK" 1;
|
font-variation-settings: "opsz" 144, "SOFT" 100, "WONK" 1;
|
||||||
@@ -191,11 +197,14 @@ body::before {
|
|||||||
}
|
}
|
||||||
.section__numwrap { display: flex; flex-direction: column; gap: 0.2rem; align-self: end; }
|
.section__numwrap { display: flex; flex-direction: column; gap: 0.2rem; align-self: end; }
|
||||||
.section__numwrap small {
|
.section__numwrap small {
|
||||||
font-family: var(--mono);
|
font-family: var(--display);
|
||||||
font-size: var(--fs-mini);
|
font-size: var(--fs-md);
|
||||||
text-transform: uppercase;
|
font-style: italic;
|
||||||
letter-spacing: 0.18em;
|
font-variation-settings: "opsz" 36, "SOFT" 100;
|
||||||
|
letter-spacing: 0;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.section__num {
|
.section__num {
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
@@ -299,7 +308,13 @@ body::before {
|
|||||||
.card--link .favicon[data-fallback] { font-family: var(--display); font-weight: 500; font-size: 1.7rem; color: var(--ink); }
|
.card--link .favicon[data-fallback] { font-family: var(--display); font-weight: 500; font-size: 1.7rem; color: var(--ink); }
|
||||||
.card__title { font-weight: 540; letter-spacing: -0.005em; line-height: 1.25; }
|
.card__title { font-weight: 540; letter-spacing: -0.005em; line-height: 1.25; }
|
||||||
.card__desc { color: var(--muted); font-size: var(--fs-sm); }
|
.card__desc { color: var(--muted); font-size: var(--fs-sm); }
|
||||||
.card__host { font-family: var(--mono); font-size: var(--fs-mini); color: var(--muted); margin-top: 0.2rem; text-transform: lowercase; }
|
.card__host { font-family: var(--mono); font-size: var(--fs-mini); color: var(--muted); text-transform: lowercase; }
|
||||||
|
.card__meta { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem 0.8rem; margin-top: 0.2rem; }
|
||||||
|
.card__date { font-family: var(--mono); font-size: var(--fs-mini); color: var(--muted); letter-spacing: 0.02em; text-transform: uppercase; }
|
||||||
|
.card__meta .card__date::before { content: "·"; margin-right: 0.5rem; color: var(--accent); }
|
||||||
|
.card__meta .card__date:only-child::before { content: none; }
|
||||||
|
.tags .card__date { padding: 3px 0; margin-left: auto; }
|
||||||
|
.portfolio__caption .card__date { margin-top: 0.25rem; }
|
||||||
|
|
||||||
/* project card */
|
/* project card */
|
||||||
.card--project { padding: 1.5rem; gap: 0.75rem; }
|
.card--project { padding: 1.5rem; gap: 0.75rem; }
|
||||||
@@ -425,7 +440,7 @@ body::before {
|
|||||||
box-shadow: 0 1px 0 rgba(20,18,34,0.04), 0 14px 28px -18px rgba(20,18,34,0.28);
|
box-shadow: 0 1px 0 rgba(20,18,34,0.04), 0 14px 28px -18px rgba(20,18,34,0.28);
|
||||||
}
|
}
|
||||||
.client__logo img.is-favicon { width: 60%; height: 60%; object-fit: contain; }
|
.client__logo img.is-favicon { width: 60%; height: 60%; object-fit: contain; }
|
||||||
.client__logo img:not(.is-favicon) { width: 100%; height: 100%; object-fit: cover; }
|
.client__logo img:not(.is-favicon) { width: 86%; height: 86%; object-fit: contain; }
|
||||||
.client__logo[data-fallback] { font-family: var(--display); font-weight: 500; font-size: 2.2rem; color: var(--ink); }
|
.client__logo[data-fallback] { font-family: var(--display); font-weight: 500; font-size: 2.2rem; color: var(--ink); }
|
||||||
.client__title {
|
.client__title {
|
||||||
font-size: var(--fs-sm);
|
font-size: var(--fs-sm);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
--ink: oklch(0.10 0 0);
|
--ink: oklch(0.10 0 0);
|
||||||
--ink-2: oklch(0.18 0 0);
|
--ink-2: oklch(0.18 0 0);
|
||||||
--muted: oklch(0.42 0 0);
|
--muted: oklch(0.42 0 0);
|
||||||
--rule: oklch(0.10 0 0);
|
--rule: oklch(0.78 0 0);
|
||||||
--accent: #DC2127;
|
--accent: #DC2127;
|
||||||
--on-accent: oklch(0.99 0 0);
|
--on-accent: oklch(0.99 0 0);
|
||||||
--display: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
--display: "Archivo", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
--ink: oklch(0.985 0 0);
|
--ink: oklch(0.985 0 0);
|
||||||
--ink-2: oklch(0.92 0 0);
|
--ink-2: oklch(0.92 0 0);
|
||||||
--muted: oklch(0.62 0 0);
|
--muted: oklch(0.62 0 0);
|
||||||
--rule: oklch(0.985 0 0);
|
--rule: oklch(0.32 0 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"][data-theme="dark"] {
|
:root[data-template="swiss"][data-theme="dark"] {
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
--ink: oklch(0.985 0 0);
|
--ink: oklch(0.985 0 0);
|
||||||
--ink-2: oklch(0.92 0 0);
|
--ink-2: oklch(0.92 0 0);
|
||||||
--muted: oklch(0.62 0 0);
|
--muted: oklch(0.62 0 0);
|
||||||
--rule: oklch(0.985 0 0);
|
--rule: oklch(0.32 0 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* clean off the editorial paper grain */
|
/* clean off the editorial paper grain */
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
/* ───── marker bar ───── */
|
/* ───── marker bar ───── */
|
||||||
:root[data-template="swiss"] .marker {
|
:root[data-template="swiss"] .marker {
|
||||||
border-bottom: 2px solid var(--ink);
|
border-bottom: 1px solid var(--rule);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
:root[data-template="swiss"] .marker__year { color: var(--accent); }
|
:root[data-template="swiss"] .marker__year { color: var(--accent); }
|
||||||
:root[data-template="swiss"] .theme {
|
:root[data-template="swiss"] .theme {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-color: var(--ink);
|
border-color: var(--rule);
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -74,15 +74,22 @@
|
|||||||
:root[data-template="swiss"] .hero__name {
|
:root[data-template="swiss"] .hero__name {
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 900;
|
font-weight: 700;
|
||||||
font-variation-settings: normal;
|
font-variation-settings: normal;
|
||||||
text-transform: uppercase;
|
text-transform: none;
|
||||||
letter-spacing: -0.045em;
|
letter-spacing: -0.035em;
|
||||||
line-height: 0.84;
|
line-height: 0.86;
|
||||||
|
}
|
||||||
|
:root[data-template="swiss"] .hero__name .hero__name-first {
|
||||||
|
font-family: var(--display);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.78em;
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .hero__name em {
|
:root[data-template="swiss"] .hero__name em {
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 900;
|
font-weight: 800;
|
||||||
font-variation-settings: normal;
|
font-variation-settings: normal;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
@@ -122,8 +129,8 @@
|
|||||||
/* ───── social ───── */
|
/* ───── social ───── */
|
||||||
:root[data-template="swiss"] .social a {
|
:root[data-template="swiss"] .social a {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-color: var(--ink);
|
border-color: var(--rule);
|
||||||
border-width: 1.5px;
|
border-width: 1px;
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -141,13 +148,15 @@
|
|||||||
|
|
||||||
/* ───── sections ───── */
|
/* ───── sections ───── */
|
||||||
:root[data-template="swiss"] .section__head {
|
:root[data-template="swiss"] .section__head {
|
||||||
border-bottom: 4px solid var(--ink);
|
border-bottom: 2px solid var(--ink);
|
||||||
padding-bottom: 0.85rem;
|
padding-bottom: 0.85rem;
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .section__numwrap small {
|
:root[data-template="swiss"] .section__numwrap small {
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
|
font-variation-settings: normal;
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .section__num {
|
:root[data-template="swiss"] .section__num {
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
@@ -160,9 +169,9 @@
|
|||||||
:root[data-template="swiss"] .section__title {
|
:root[data-template="swiss"] .section__title {
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 900;
|
font-weight: 800;
|
||||||
font-variation-settings: normal;
|
font-variation-settings: normal;
|
||||||
text-transform: uppercase;
|
text-transform: none;
|
||||||
letter-spacing: -0.025em;
|
letter-spacing: -0.025em;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
@@ -180,8 +189,8 @@
|
|||||||
/* ───── cards ───── */
|
/* ───── cards ───── */
|
||||||
:root[data-template="swiss"] .card {
|
:root[data-template="swiss"] .card {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-color: var(--ink);
|
border: 0;
|
||||||
background: var(--paper);
|
background: var(--paper-2);
|
||||||
transition: background 200ms cubic-bezier(0.22, 1, 0.36, 1), color 200ms cubic-bezier(0.22, 1, 0.36, 1);
|
transition: background 200ms cubic-bezier(0.22, 1, 0.36, 1), color 200ms cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
}
|
}
|
||||||
@@ -190,7 +199,6 @@
|
|||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background: var(--ink);
|
background: var(--ink);
|
||||||
color: var(--paper);
|
color: var(--paper);
|
||||||
border-color: var(--ink);
|
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .card:hover .card__title,
|
:root[data-template="swiss"] .card:hover .card__title,
|
||||||
:root[data-template="swiss"] .card:hover .card__desc,
|
:root[data-template="swiss"] .card:hover .card__desc,
|
||||||
@@ -205,8 +213,8 @@
|
|||||||
:root[data-template="swiss"] .card__title {
|
:root[data-template="swiss"] .card__title {
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: none;
|
||||||
letter-spacing: -0.005em;
|
letter-spacing: -0.01em;
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .card__desc {
|
:root[data-template="swiss"] .card__desc {
|
||||||
font-family: var(--body);
|
font-family: var(--body);
|
||||||
@@ -218,16 +226,25 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
}
|
}
|
||||||
|
:root[data-template="swiss"] .card__date {
|
||||||
|
font-family: var(--display);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--muted);
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
}
|
||||||
|
:root[data-template="swiss"] .card:hover .card__date,
|
||||||
|
:root[data-template="swiss"] .card:hover .card__host { color: var(--paper); }
|
||||||
|
:root[data-template="swiss"] .card__meta .card__date::before { color: var(--accent); }
|
||||||
|
:root[data-template="swiss"] .card:hover .card__meta .card__date::before { color: var(--paper); }
|
||||||
|
|
||||||
/* link card favicon */
|
/* link card favicon */
|
||||||
:root[data-template="swiss"] .card--link .favicon {
|
:root[data-template="swiss"] .card--link .favicon {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-color: var(--ink);
|
border: 0;
|
||||||
background: var(--paper);
|
background: var(--paper);
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .card--link:hover .favicon {
|
:root[data-template="swiss"] .card--link:hover .favicon {
|
||||||
background: var(--paper);
|
background: var(--paper);
|
||||||
border-color: var(--paper);
|
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .card--link .favicon[data-fallback] {
|
:root[data-template="swiss"] .card--link .favicon[data-fallback] {
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
@@ -238,19 +255,20 @@
|
|||||||
/* project card */
|
/* project card */
|
||||||
:root[data-template="swiss"] .card--project .card__title {
|
:root[data-template="swiss"] .card--project .card__title {
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-weight: 900;
|
font-weight: 800;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-variation-settings: normal;
|
font-variation-settings: normal;
|
||||||
text-transform: uppercase;
|
text-transform: none;
|
||||||
font-size: var(--fs-lg);
|
font-size: var(--fs-lg);
|
||||||
line-height: 0.95;
|
line-height: 1.05;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* featured — solid red, white text, true poster */
|
/* featured — solid red, white text, true poster */
|
||||||
:root[data-template="swiss"] .card--featured {
|
:root[data-template="swiss"] .card--featured {
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
color: var(--on-accent);
|
color: var(--on-accent);
|
||||||
border-color: var(--ink);
|
border: 0;
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .card--featured:hover {
|
:root[data-template="swiss"] .card--featured:hover {
|
||||||
background: var(--ink);
|
background: var(--ink);
|
||||||
@@ -263,9 +281,11 @@
|
|||||||
}
|
}
|
||||||
:root[data-template="swiss"] .card--featured .card__title {
|
:root[data-template="swiss"] .card--featured .card__title {
|
||||||
color: var(--on-accent);
|
color: var(--on-accent);
|
||||||
font-weight: 900;
|
font-weight: 800;
|
||||||
text-transform: uppercase;
|
text-transform: none;
|
||||||
font-size: clamp(1.95rem, 1.30rem + 1.8vw, 2.8rem);
|
font-size: clamp(1.95rem, 1.30rem + 1.8vw, 2.8rem);
|
||||||
|
letter-spacing: -0.025em;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .card--featured:hover .card__title,
|
:root[data-template="swiss"] .card--featured:hover .card__title,
|
||||||
:root[data-template="swiss"] .card--featured:hover .card__desc { color: var(--paper); }
|
:root[data-template="swiss"] .card--featured:hover .card__desc { color: var(--paper); }
|
||||||
@@ -276,7 +296,7 @@
|
|||||||
/* tags — square, monoline, all caps */
|
/* tags — square, monoline, all caps */
|
||||||
:root[data-template="swiss"] .tag {
|
:root[data-template="swiss"] .tag {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-color: var(--ink);
|
border: 0;
|
||||||
background: var(--paper);
|
background: var(--paper);
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -287,7 +307,6 @@
|
|||||||
:root[data-template="swiss"] .card:hover .tag {
|
:root[data-template="swiss"] .card:hover .tag {
|
||||||
background: var(--paper);
|
background: var(--paper);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
border-color: var(--paper);
|
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .card--featured .tag {
|
:root[data-template="swiss"] .card--featured .tag {
|
||||||
background: color-mix(in oklch, var(--on-accent) 14%, transparent);
|
background: color-mix(in oklch, var(--on-accent) 14%, transparent);
|
||||||
@@ -303,7 +322,7 @@
|
|||||||
/* youtube */
|
/* youtube */
|
||||||
:root[data-template="swiss"] .card--youtube {
|
:root[data-template="swiss"] .card--youtube {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-color: var(--ink);
|
border: 0;
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .yt__play {
|
:root[data-template="swiss"] .yt__play {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@@ -318,37 +337,40 @@
|
|||||||
:root[data-template="swiss"] .yt__title {
|
:root[data-template="swiss"] .yt__title {
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
text-transform: none;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: -0.005em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* portfolio */
|
/* portfolio */
|
||||||
:root[data-template="swiss"] .card--portfolio {
|
:root[data-template="swiss"] .card--portfolio {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-color: var(--ink);
|
border: 0;
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .card--portfolio:hover { background: var(--paper); color: var(--ink); }
|
:root[data-template="swiss"] .card--portfolio:hover { background: var(--paper-2); color: var(--ink); }
|
||||||
:root[data-template="swiss"] .card--portfolio:hover .portfolio__caption .card__title { color: var(--accent); }
|
:root[data-template="swiss"] .card--portfolio:hover .portfolio__caption .card__title { color: var(--accent); }
|
||||||
|
:root[data-template="swiss"] .card--portfolio:hover .card__desc,
|
||||||
|
:root[data-template="swiss"] .card--portfolio:hover .card__date,
|
||||||
|
:root[data-template="swiss"] .card--portfolio:hover .card__host { color: var(--muted); }
|
||||||
:root[data-template="swiss"] .portfolio__caption {
|
:root[data-template="swiss"] .portfolio__caption {
|
||||||
border-top: 2px solid var(--ink);
|
border-top: 0;
|
||||||
padding: 1.1rem 1.35rem 1.2rem;
|
padding: 1.1rem 1.35rem 1.2rem;
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .portfolio__caption .card__title {
|
:root[data-template="swiss"] .portfolio__caption .card__title {
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-weight: 900;
|
font-weight: 800;
|
||||||
font-variation-settings: normal;
|
font-variation-settings: normal;
|
||||||
text-transform: uppercase;
|
text-transform: none;
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
font-size: var(--fs-lg);
|
font-size: var(--fs-lg);
|
||||||
line-height: 1;
|
line-height: 1.05;
|
||||||
}
|
}
|
||||||
:root[data-template="swiss"] .portfolio__caption .card__desc { color: var(--muted); }
|
:root[data-template="swiss"] .portfolio__caption .card__desc { color: var(--muted); }
|
||||||
|
|
||||||
/* clients */
|
/* clients */
|
||||||
:root[data-template="swiss"] .client__logo {
|
:root[data-template="swiss"] .client__logo {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
border-color: var(--ink);
|
border: 0;
|
||||||
background: var(--paper);
|
background: var(--paper-2);
|
||||||
}
|
}
|
||||||
/* opt the client tile out of the global card hover-flip: the card has no
|
/* opt the client tile out of the global card hover-flip: the card has no
|
||||||
chrome, so filling it with ink would hide the title text below the logo */
|
chrome, so filling it with ink would hide the title text below the logo */
|
||||||
@@ -360,7 +382,6 @@
|
|||||||
:root[data-template="swiss"] .card--client:hover .client__title { color: var(--accent); }
|
:root[data-template="swiss"] .card--client:hover .client__title { color: var(--accent); }
|
||||||
:root[data-template="swiss"] .card--client:hover .client__logo {
|
:root[data-template="swiss"] .card--client:hover .client__logo {
|
||||||
background: var(--ink);
|
background: var(--ink);
|
||||||
border-color: var(--ink);
|
|
||||||
transform: none;
|
transform: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
@@ -374,15 +395,15 @@
|
|||||||
:root[data-template="swiss"] .client__title {
|
:root[data-template="swiss"] .client__title {
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
text-transform: none;
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0;
|
||||||
font-size: var(--fs-mini);
|
font-size: var(--fs-sm);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* footer */
|
/* footer */
|
||||||
:root[data-template="swiss"] .foot {
|
:root[data-template="swiss"] .foot {
|
||||||
border-top: 2px solid var(--ink);
|
border-top: 1px solid var(--rule);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -391,7 +412,7 @@
|
|||||||
font-family: var(--display);
|
font-family: var(--display);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: 900;
|
font-weight: 800;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
font-size: var(--fs-md);
|
font-size: var(--fs-md);
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
|
|||||||
@@ -38,7 +38,10 @@
|
|||||||
<span>
|
<span>
|
||||||
<span class="card__title">${esc(it.title)}</span>
|
<span class="card__title">${esc(it.title)}</span>
|
||||||
${it.description ? `<span class="card__desc">${esc(it.description)}</span>` : ""}
|
${it.description ? `<span class="card__desc">${esc(it.description)}</span>` : ""}
|
||||||
${host ? `<span class="card__host">${esc(host)}</span>` : ""}
|
<span class="card__meta">
|
||||||
|
${host ? `<span class="card__host">${esc(host)}</span>` : ""}
|
||||||
|
${dateMarkup(it)}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>`;
|
</a>`;
|
||||||
}
|
}
|
||||||
@@ -52,7 +55,7 @@
|
|||||||
<${tag} class="card card--project${featured} reveal" ${attrs}>
|
<${tag} class="card card--project${featured} reveal" ${attrs}>
|
||||||
<span class="card__title">${esc(it.title)}</span>
|
<span class="card__title">${esc(it.title)}</span>
|
||||||
${it.description ? `<p class="card__desc">${esc(it.description)}</p>` : ""}
|
${it.description ? `<p class="card__desc">${esc(it.description)}</p>` : ""}
|
||||||
${tags ? `<div class="tags">${tags}</div>` : ""}
|
${(tags || it.date) ? `<div class="tags">${tags}${dateMarkup(it)}</div>` : ""}
|
||||||
</${tag}>`;
|
</${tag}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +66,7 @@
|
|||||||
<div class="yt__play" aria-hidden="true">
|
<div class="yt__play" aria-hidden="true">
|
||||||
<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z" fill="currentColor"/></svg>
|
<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z" fill="currentColor"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="yt__title">${esc(it.title)}</div>
|
<div class="yt__title">${esc(it.title)}${it.date ? ` <span class="yt__date">${esc(fmtDate(it.date))}</span>` : ""}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -78,10 +81,11 @@
|
|||||||
<div class="portfolio__media"${ratio}>
|
<div class="portfolio__media"${ratio}>
|
||||||
${src ? `<img loading="lazy" alt="${esc(it.title || "")}" src="${esc(src)}">` : ""}
|
${src ? `<img loading="lazy" alt="${esc(it.title || "")}" src="${esc(src)}">` : ""}
|
||||||
</div>
|
</div>
|
||||||
${(it.title || it.description) ? `
|
${(it.title || it.description || it.date) ? `
|
||||||
<div class="portfolio__caption">
|
<div class="portfolio__caption">
|
||||||
${it.title ? `<span class="card__title">${esc(it.title)}</span>` : ""}
|
${it.title ? `<span class="card__title">${esc(it.title)}</span>` : ""}
|
||||||
${it.description ? `<span class="card__desc">${esc(it.description)}</span>` : ""}
|
${it.description ? `<span class="card__desc">${esc(it.description)}</span>` : ""}
|
||||||
|
${dateMarkup(it)}
|
||||||
</div>` : ""}
|
</div>` : ""}
|
||||||
</${tag}>`;
|
</${tag}>`;
|
||||||
}
|
}
|
||||||
@@ -102,6 +106,7 @@
|
|||||||
: esc(initial)}
|
: esc(initial)}
|
||||||
</span>
|
</span>
|
||||||
${it.title ? `<span class="client__title">${esc(it.title)}</span>` : ""}
|
${it.title ? `<span class="client__title">${esc(it.title)}</span>` : ""}
|
||||||
|
${dateMarkup(it)}
|
||||||
</${tag}>`;
|
</${tag}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +145,8 @@
|
|||||||
rss: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 3a16 16 0 0 1 16 16h-3A13 13 0 0 0 5 6V3Zm0 7a9 9 0 0 1 9 9h-3a6 6 0 0 0-6-6v-3Zm1.5 6a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z"/></svg>',
|
rss: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M5 3a16 16 0 0 1 16 16h-3A13 13 0 0 0 5 6V3Zm0 7a9 9 0 0 1 9 9h-3a6 6 0 0 0-6-6v-3Zm1.5 6a2.5 2.5 0 1 1 0 5 2.5 2.5 0 0 1 0-5Z"/></svg>',
|
||||||
link: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1"/><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1"/></svg>',
|
link: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-1 1"/><path d="M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l1-1"/></svg>',
|
||||||
calendar: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days-icon lucide-calendar-days"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/><path d="M8 14h.01"/><path d="M12 14h.01"/><path d="M16 14h.01"/><path d="M8 18h.01"/><path d="M12 18h.01"/><path d="M16 18h.01"/></svg>',
|
calendar: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-calendar-days-icon lucide-calendar-days"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/><path d="M8 14h.01"/><path d="M12 14h.01"/><path d="M16 14h.01"/><path d="M8 18h.01"/><path d="M12 18h.01"/><path d="M16 18h.01"/></svg>',
|
||||||
bluesky: '<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Bluesky</title><path d="M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"/></svg>'
|
bluesky: '<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Bluesky</title><path d="M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"/></svg>',
|
||||||
|
lastfm: '<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Last.fm</title><path d="M10.584 17.21l-.88-2.392s-1.43 1.594-3.573 1.594c-1.897 0-3.244-1.649-3.244-4.288 0-3.382 1.704-4.591 3.381-4.591 2.42 0 3.189 1.567 3.849 3.574l.88 2.749c.88 2.666 2.529 4.81 7.285 4.81 3.409 0 5.718-1.044 5.718-3.793 0-2.227-1.265-3.381-3.63-3.931l-1.758-.385c-1.21-.275-1.567-.77-1.567-1.595 0-.934.742-1.484 1.952-1.484 1.32 0 2.034.495 2.144 1.677l2.749-.33c-.22-2.474-1.924-3.492-4.729-3.492-2.474 0-4.893.935-4.893 3.932 0 1.87.907 3.051 3.189 3.601l1.87.44c1.402.33 1.869.907 1.869 1.704 0 1.017-.99 1.43-2.86 1.43-2.776 0-3.93-1.457-4.59-3.464l-.907-2.75c-1.155-3.573-2.997-4.893-6.653-4.893C2.144 5.333 0 7.89 0 12.233c0 4.18 2.144 6.434 5.993 6.434 3.106 0 4.591-1.457 4.591-1.457z"/></svg>'
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderSocial(items) {
|
function renderSocial(items) {
|
||||||
@@ -152,11 +158,24 @@
|
|||||||
function nameMarkup(name) {
|
function nameMarkup(name) {
|
||||||
if (!name) return "";
|
if (!name) return "";
|
||||||
const parts = String(name).trim().split(/\s+/);
|
const parts = String(name).trim().split(/\s+/);
|
||||||
if (parts.length < 2) return esc(name);
|
if (parts.length < 2) return `<span class="hero__name-first">${esc(name)}</span>`;
|
||||||
const last = parts.pop();
|
const last = parts.pop();
|
||||||
return `${esc(parts.join(" "))} <em>${esc(last)}</em>`;
|
return `<span class="hero__name-first">${esc(parts.join(" "))}</span> <em>${esc(last)}</em>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fmtDate(d) {
|
||||||
|
if (!d) return "";
|
||||||
|
const m = String(d).match(/^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/);
|
||||||
|
if (!m) return String(d);
|
||||||
|
const [, y, mo, dd] = m;
|
||||||
|
const months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
||||||
|
if (dd && mo) return `${months[+mo - 1]} ${+dd}, ${y}`;
|
||||||
|
if (mo) return `${months[+mo - 1]} ${y}`;
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
const dateMarkup = (it) =>
|
||||||
|
it.date ? `<time class="card__date" datetime="${esc(it.date)}">${esc(fmtDate(it.date))}</time>` : "";
|
||||||
|
|
||||||
function attachFaviconFallback(root) {
|
function attachFaviconFallback(root) {
|
||||||
$$("img[data-fallback-initial]", root).forEach(img => {
|
$$("img[data-fallback-initial]", root).forEach(img => {
|
||||||
img.addEventListener("error", () => {
|
img.addEventListener("error", () => {
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
"kicker": "Where I live online",
|
"kicker": "Where I live online",
|
||||||
"items": [
|
"items": [
|
||||||
{ "type": "link", "title": "Personal site", "url": "https://example.com", "description": "What I do, who I am" },
|
{ "type": "link", "title": "Personal site", "url": "https://example.com", "description": "What I do, who I am" },
|
||||||
{ "type": "link", "title": "Notes", "url": "https://notes.example.com", "description": "Long-form writing" },
|
{ "type": "link", "title": "Notes", "url": "https://notes.example.com", "description": "Long-form writing", "date": "2025-09" },
|
||||||
{ "type": "link", "title": "Lab", "url": "https://lab.example.com", "description": "Experiments & sketches" }
|
{ "type": "link", "title": "Lab", "url": "https://lab.example.com", "description": "Experiments & sketches", "date": "2024" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -32,14 +32,16 @@
|
|||||||
"title": "Analytical Engine",
|
"title": "Analytical Engine",
|
||||||
"url": "https://example.com/projects/engine",
|
"url": "https://example.com/projects/engine",
|
||||||
"description": "An early proposal for a general-purpose computing machine, with looping and conditional branching.",
|
"description": "An early proposal for a general-purpose computing machine, with looping and conditional branching.",
|
||||||
"tags": ["computing", "research"]
|
"tags": ["computing", "research"],
|
||||||
|
"date": "1837"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "card",
|
"type": "card",
|
||||||
"title": "Bernoulli Numbers",
|
"title": "Bernoulli Numbers",
|
||||||
"url": "https://example.com/projects/bernoulli",
|
"url": "https://example.com/projects/bernoulli",
|
||||||
"description": "Method for computing Bernoulli numbers using the Engine.",
|
"description": "Method for computing Bernoulli numbers using the Engine.",
|
||||||
"tags": ["mathematics"]
|
"tags": ["mathematics"],
|
||||||
|
"date": "1843-07"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -53,7 +55,8 @@
|
|||||||
"title": "Notes G — diagrams for publication",
|
"title": "Notes G — diagrams for publication",
|
||||||
"url": "https://example.com/showcase/notes-g",
|
"url": "https://example.com/showcase/notes-g",
|
||||||
"image": "https://images.unsplash.com/photo-1518770660439-4636190af475?w=1600&q=80",
|
"image": "https://images.unsplash.com/photo-1518770660439-4636190af475?w=1600&q=80",
|
||||||
"description": "Tables and diagrams prepared for the 1843 translation."
|
"description": "Tables and diagrams prepared for the 1843 translation.",
|
||||||
|
"date": "1843-10-15"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<!-- Editorial: Fraunces (variable serif) + Geist (refined sans) + Geist Mono
|
<!-- Editorial: Fraunces (variable serif) + Geist (refined sans) + Geist Mono
|
||||||
Swiss: Archivo (variable grotesque, near-Akzidenz/Univers) -->
|
Swiss: Archivo (variable grotesque, near-Akzidenz/Univers) -->
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700;900&family=Fraunces:opsz,wght,SOFT,WONK@9..144,300..600,30..100,0..1&family=Geist:wght@300;400;500;600&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Archivo:wght@400;500;600;700;900&family=DM+Serif+Display:ital@0;1&family=Fraunces:opsz,wght,SOFT,WONK@9..144,300..600,30..100,0..1&family=Geist:wght@300;400;500;600&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Apply saved theme + template before paint to avoid flash
|
// Apply saved theme + template before paint to avoid flash
|
||||||
|
|||||||
Reference in New Issue
Block a user