diff --git a/src/lib/format.ts b/src/lib/format.ts index 5405d12..7eb1b91 100644 Binary files a/src/lib/format.ts and b/src/lib/format.ts differ diff --git a/src/pages/pulse.astro b/src/pages/pulse.astro index ac05c56..b4af635 100644 --- a/src/pages/pulse.astro +++ b/src/pages/pulse.astro @@ -3,14 +3,14 @@ import AppLayout from '../layouts/AppLayout.astro'; import Avatar from '../components/Avatar.astro'; import AvatarPile from '../components/AvatarPile.astro'; import { - getUpcomingEvents, getPastEvents, getEventBySlug, getEventAttendees, - getEventRsvpCount, getUserRsvp, setEventRsvp, recordActivity, + getUpcomingEvents, getEventBySlug, getEventAttendees, + getUserRsvp, setEventRsvp, recordActivity, getAllRoadmapItems, getLatestPublishedDispatches, getAllCabMembers, } from '../lib/db'; import { - pulseDateLabel, timeOfDay, tenureSince, pigmentForId, relativeTime, - eventKindLabel, defaultActionLabel, - dispatchSlug, dispatchKindLabel, dispatchKindPigment, dispatchExcerptParas, + pulseDateLabel, timeOfDay, tenureSince, relativeTime, + eventKindLabel, + dispatchSlug, dispatchKindLabel, dispatchKindPigment, dispatchLongPreview, } from '../lib/format'; const user = Astro.locals.user; @@ -36,7 +36,7 @@ if (Astro.request.method === 'POST') { // ── Greeting ─────────────────────────────────────────────────────── const firstName = user.name.split(' ')[0]; const greeting = `Good ${timeOfDay()}, ${firstName}.`; -const dateLabel = pulseDateLabel(); +// (date label dropped per the v3 eyebrow-removal pass; tenure line stays inline) const tenureAnchor = user.role === 'cab' && user.cab_joined_date ? user.cab_joined_date @@ -47,7 +47,6 @@ const tenure = tenureSince(tenureAnchor); const upcoming = getUpcomingEvents(20); const hero = upcoming.find(e => e.kind !== 'office_hours') ?? upcoming[0] ?? null; const comingUp = upcoming.filter(e => e.id !== hero?.id).slice(0, 4); -const past = getPastEvents(4); function parseUtc(s: string): Date { if (/T.*[Zz]$/.test(s) || /[+-]\d{2}:?\d{2}$/.test(s)) return new Date(s); @@ -64,12 +63,12 @@ const timeStr = (iso: string) => fmt({ hour: '2-digit', minute: '2-digit', ho const heroAttendees = hero ? getEventAttendees(hero.slug, 'yes') : []; const heroConfirmedCount = heroAttendees.length; const heroMyRsvp = hero ? getUserRsvp(user.id, hero.slug) : null; -const heroAudience = hero?.audience ?? 'Members only'; -// ── Latest from Fenja (single most recent dispatch) ──────────────── +// ── Latest from Fenja ────────────────────────────────────────────── const [latestDispatch] = getLatestPublishedDispatches(1); +const latestPreview = latestDispatch ? dispatchLongPreview(latestDispatch, 520) : ''; -// ── Roadmap preview (3 most-recently-updated items) ──────────────── +// ── Roadmap preview (3 most-recently-updated items, horizontal) ──── const roadmapPreview = getAllRoadmapItems() .sort((a, b) => (b.updated_at > a.updated_at ? 1 : -1)) .slice(0, 3); @@ -90,7 +89,7 @@ function roadmapStatusBlurb(item: { status: 'shipping' | 'beta' | 'exploring'; t } } -// ── Members strip — all cab users in member-number order ─────────── +// ── Council members ───────────────────────────────────────────────── const members = getAllCabMembers(); --- @@ -98,44 +97,36 @@ const members = getAllCabMembers();
-

{dateLabel}

-

- {greeting} -

+

{greeting}

- You've been a member for {tenure}. The team is reading every note you leave. + You've been a member for {tenure}. The team is reading every note you leave.

- + {hero ? (
-
- Next up · {heroAudience} - Invitation by hand -
- -
-
- {weekday(hero.starts_at)} - {dayNum(hero.starts_at)} - {monthShort(hero.starts_at)} +
+
+ {weekday(hero.starts_at)} + {dayNum(hero.starts_at)} + {monthShort(hero.starts_at)}
-
-

{hero.title}

-

{hero.description}

-

+

+

{hero.title}

+

{hero.description}

+

{hero.location}{hero.location && ' · '}{timeStr(hero.starts_at)}{hero.duration_label ? ` · ${hero.duration_label}` : ''}

-
-
- +
+
+ {hero.capacity ? `${hero.capacity} seats · ` : ''}{heroConfirmedCount} confirmed {heroAttendees.length > 0 && ( @@ -143,168 +134,106 @@ const members = getAllCabMembers(); )}
-
+ {heroMyRsvp === 'yes' ? ( <> - You're confirmed ✓ - + You're confirmed ✓ + ) : ( - + )}
- + {comingUp.length > 0 && ( - <> -
-

Also coming up

-
    - {comingUp.map(ev => ( -
  • - - {dayNum(ev.starts_at)} - {monthShort(ev.starts_at)} - - - {ev.title} - {[ev.duration_label, ev.audience, eventKindLabel(ev.kind)].filter(Boolean).join(' · ')} - -
    - - - -
    -
  • - ))} -
- - )} - - - {past.length > 0 && ( - <> -
-
-

Past gatherings

- View all → -
-
    - {past.map(ev => { - const attended = getEventRsvpCount(ev.slug).going; - const hasNotes = !!ev.notes_url; - return ( -
  • - - {dayNum(ev.starts_at)} - {monthShort(ev.starts_at)} - - - {ev.title} - {attended} attended · {hasNotes ? 'Notes shared' : 'No notes'} - - {hasNotes && ( - Notes ↗ - )} -
  • - ); - })} -
- - )} -
- ) : ( -
-

Nothing scheduled yet — when we have something, you'll be the first to know.

-
- )} - - -
- - {latestDispatch && ( -
-
-

Latest from Fenja

- All updates → -
- -
- - -

{latestDispatch.title}

- -

{dispatchExcerptParas(latestDispatch).lead}

- - Read the full dispatch → -
-
- )} - - {latestDispatch && roadmapPreview.length > 0 &&
} - -
-
-

From the roadmap

- See the full roadmap → -
- - {roadmapPreview.length === 0 ? ( -

No roadmap items yet.

- ) : ( -
    - {roadmapPreview.map(item => ( -
  • - -
    -

    {item.title}

    -

    {roadmapStatusBlurb(item)}

    +
      + {comingUp.map(ev => ( +
    • +
      + {dayNum(ev.starts_at)} + {monthShort(ev.starts_at)} +
      +
      +

      {ev.title}

      +

      {[ev.duration_label, ev.audience, eventKindLabel(ev.kind)].filter(Boolean).join(' · ')}

    • ))}
    )} -
    -
- - {members.length > 0 && ( -
-
-

The council

- See who our council is made up of → + See all events → + +
+ ) : ( +
+

Nothing scheduled yet — when we have something, you'll be the first to know.

+ See all events → +
+ )} + + + {latestDispatch && ( +
+ -
+ )} + + + {roadmapPreview.length > 0 && ( +
+ + See the full roadmap → +
+ )} + + + {members.length > 0 && ( +
+ + See who our council is made up of →
)} @@ -318,7 +247,7 @@ const members = getAllCabMembers(); margin: 0 auto; display: flex; flex-direction: column; - gap: var(--space-8); + gap: var(--space-10); } /* ── Cascade entry (first paint only) ─────────────────────────── */ @@ -331,20 +260,14 @@ const members = getAllCabMembers(); .cascade:nth-child(2) { animation-delay: 100ms; } .cascade:nth-child(3) { animation-delay: 200ms; } .cascade:nth-child(4) { animation-delay: 300ms; } - @keyframes cascade-in { - to { opacity: 1; transform: translateY(0); } - } + .cascade:nth-child(5) { animation-delay: 400ms; } + @keyframes cascade-in { to { opacity: 1; transform: translateY(0); } } @media (prefers-reduced-motion: reduce) { .cascade { opacity: 1; transform: none; animation: none; } } /* ── Greeting ─────────────────────────────────────────────────── */ .greeting { display: flex; flex-direction: column; gap: var(--space-3); } - .date-label { - letter-spacing: var(--tracking-wider); - text-transform: uppercase; - color: var(--on-surface-muted); - } .greeting-line { font-family: var(--font-serif); font-weight: 400; @@ -354,105 +277,90 @@ const members = getAllCabMembers(); color: var(--on-surface); margin: 0; } - .greeting-italic { font-style: italic; } .greeting-sub { color: var(--on-surface-variant); max-width: 48rem; margin: 0; } - .greeting-sub em { font-style: italic; color: var(--on-surface); } - /* ── Events card (top, --ink) ─────────────────────────────────── */ + /* ── Events card (--ink) ──────────────────────────────────────── */ .events-card { background: var(--ink); color: var(--ink-text); border-radius: var(--radius-lg); - padding: 1.75rem; + padding: var(--space-7) var(--space-8); display: flex; flex-direction: column; - gap: var(--space-5); + gap: var(--space-6); } .events-card--empty { - align-items: stretch; - justify-content: center; - text-align: center; - min-height: 200px; + align-items: flex-start; + text-align: left; + min-height: 160px; + justify-content: space-between; } .events-empty-line { color: var(--ink-text); font-family: var(--font-serif); font-size: 1.25rem; - margin: auto; + margin: 0; max-width: 32rem; - } - .events-empty-line em { font-style: italic; } - - .events-hero-top { - display: flex; - justify-content: space-between; - align-items: center; - color: var(--ink-muted); - } - .events-eyebrow { - font-family: var(--font-sans); - font-size: var(--text-label-sm); - letter-spacing: var(--tracking-wider); - text-transform: uppercase; + opacity: 0.92; } - .events-hero-body { + /* Hero (lighter, fewer italics) */ + .hero-body { display: grid; - grid-template-columns: 100px 1fr; - gap: var(--space-6); - padding: var(--space-4) 0; + grid-template-columns: 110px 1fr; + gap: var(--space-7); position: relative; } - .events-hero-body::after { + .hero-body::after { content: ''; position: absolute; - left: 100px; + left: 110px; top: 0; bottom: 0; width: 0.5px; - background: rgba(232, 224, 208, 0.2); + background: rgba(232, 224, 208, 0.18); } - .events-date { display: flex; flex-direction: column; gap: 2px; } - .events-weekday, .events-month { + .hero-date { display: flex; flex-direction: column; gap: 4px; } + .hero-weekday, .hero-month { font-family: var(--font-sans); font-size: var(--text-label-sm); letter-spacing: var(--tracking-wider); text-transform: uppercase; - color: var(--ink-text); + color: rgba(232, 224, 208, 0.75); } - .events-day { + .hero-day { font-family: var(--font-serif); - font-style: italic; + font-weight: 400; font-size: 2.75rem; line-height: 1; color: var(--ink-text); } - .events-detail { padding-left: var(--space-5); } - .events-title { + .hero-detail { padding-left: var(--space-6); } + .hero-title { font-family: var(--font-serif); - font-style: italic; font-weight: 400; font-size: 1.75rem; line-height: 1.2; color: var(--ink-text); margin: 0 0 var(--space-3); } - .events-desc { - color: rgba(232, 224, 208, 0.85); + .hero-desc { + color: rgba(232, 224, 208, 0.92); margin: 0 0 var(--space-3); - max-width: 40rem; + max-width: 50rem; } - .events-meta { - color: var(--ink-muted); + .hero-meta { + color: rgba(232, 224, 208, 0.7); font-size: var(--text-body-sm); margin: 0; } - .events-hero-foot { - border-top: 0.5px solid rgba(232, 224, 208, 0.2); + /* Hero foot */ + .hero-foot { + border-top: 0.5px solid rgba(232, 224, 208, 0.18); padding-top: var(--space-4); display: flex; justify-content: space-between; @@ -460,17 +368,16 @@ const members = getAllCabMembers(); gap: var(--space-4); flex-wrap: wrap; } - .events-foot-left { display: flex; align-items: center; gap: var(--space-4); } - .events-foot-stat { - color: var(--ink-muted); + .hero-foot-left { display: flex; align-items: center; gap: var(--space-4); } + .hero-foot-stat { + color: rgba(232, 224, 208, 0.7); font-family: var(--font-sans); font-size: var(--text-label-sm); letter-spacing: var(--tracking-wider); text-transform: uppercase; } - .events-foot-right { display: flex; align-items: center; gap: var(--space-3); } - - .events-cta { + .hero-foot-right { display: flex; align-items: center; gap: var(--space-3); } + .hero-cta { background: var(--ink-text); color: var(--ink); border: none; @@ -484,8 +391,8 @@ const members = getAllCabMembers(); cursor: pointer; transition: opacity var(--duration-fast) var(--ease-standard); } - .events-cta:hover { opacity: 0.85; } - .events-confirmed { + .hero-cta:hover { opacity: 0.85; } + .hero-confirmed { color: var(--ink-text); font-family: var(--font-sans); font-size: var(--text-label-md); @@ -496,10 +403,10 @@ const members = getAllCabMembers(); border: 0.5px solid rgba(232, 224, 208, 0.4); border-radius: 999px; } - .events-change { + .hero-change { background: transparent; border: none; - color: var(--ink-muted); + color: rgba(232, 224, 208, 0.75); font-family: var(--font-sans); font-size: var(--text-label-sm); letter-spacing: var(--tracking-wide); @@ -508,152 +415,62 @@ const members = getAllCabMembers(); text-decoration: underline; } - /* Sub-sections (Coming up + Past gatherings on the same blue card) */ - .events-divider { - border: none; - height: 0.5px; - background: rgba(232, 224, 208, 0.2); - margin: var(--space-3) 0 var(--space-2); - } - .events-sub-eyebrow { - font-family: var(--font-sans); - font-size: var(--text-label-sm); - letter-spacing: var(--tracking-wider); - text-transform: uppercase; - color: var(--ink-muted); - margin: 0 0 var(--space-2); - } - .events-past-head { - display: flex; - justify-content: space-between; - align-items: baseline; - gap: var(--space-3); - } - .events-past-all { - font-family: var(--font-sans); - font-size: var(--text-label-sm); - letter-spacing: var(--tracking-wide); - text-transform: uppercase; - color: var(--ink-text); - text-decoration: none; - border-bottom: none; - opacity: 0.85; - } - .events-past-all:hover { opacity: 1; border-bottom: none; } - - .events-list { + /* Bundled coming-up sub-cards (no RSVP buttons) */ + .coming-up-grid { list-style: none; padding: 0; margin: 0; - display: flex; - flex-direction: column; - } - .events-list-row { display: grid; - grid-template-columns: 60px 1fr auto; - gap: var(--space-4); - align-items: center; - padding: var(--space-3) 0; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: var(--space-3); } - .events-list-row + .events-list-row { border-top: 0.5px solid rgba(232, 224, 208, 0.1); } - - .events-list-date { display: flex; flex-direction: column; gap: 2px; } - .events-list-day { + .coming-up-card { + background: rgba(232, 224, 208, 0.06); + border: 0.5px solid rgba(232, 224, 208, 0.14); + border-radius: var(--radius-md); + padding: var(--space-4) var(--space-5); + display: flex; + gap: var(--space-4); + align-items: flex-start; + } + .cu-date { display: flex; flex-direction: column; gap: 2px; min-width: 36px; } + .cu-day { font-family: var(--font-serif); - font-style: italic; - font-size: 1.25rem; + font-size: 1.5rem; line-height: 1; color: var(--ink-text); } - .events-list-month { + .cu-month { font-family: var(--font-sans); font-size: var(--text-label-sm); letter-spacing: var(--tracking-wider); text-transform: uppercase; - color: var(--ink-muted); + color: rgba(232, 224, 208, 0.7); } - - .events-list-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; } - .events-list-title { + .cu-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; } + .cu-title { font-family: var(--font-serif); - font-style: italic; + font-weight: 400; font-size: 1rem; + line-height: 1.25; color: var(--ink-text); + margin: 0; } - .events-list-meta { + .cu-meta { font-size: 0.75rem; - color: var(--ink-muted); - } - - .events-list-action-form { justify-self: end; } - .events-list-action { - background: none; - border: none; - color: var(--ink-text); - font-family: var(--font-sans); - font-size: var(--text-label-md); - font-weight: 500; - letter-spacing: var(--tracking-wide); - text-transform: uppercase; - cursor: pointer; - padding: 0; - text-decoration: none; - border-bottom: none; - opacity: 0.85; - } - .events-list-action:hover { opacity: 1; border-bottom: none; } - - /* ── Combined card (Roadmap + Latest from Fenja) ──────────────── */ - .combined-card { - background: var(--surface-card); - border: 0.5px solid var(--surface-card-border); - border-radius: var(--radius-lg); - padding: var(--space-6); - display: flex; - flex-direction: column; - gap: var(--space-4); - } - - .sub-section { - display: flex; - flex-direction: column; - gap: var(--space-3); - } - - .sub-head { - display: flex; - justify-content: space-between; - align-items: baseline; - gap: var(--space-3); - } - - .sub-eyebrow { - letter-spacing: var(--tracking-wider); - text-transform: uppercase; - color: var(--on-surface-variant); + color: rgba(232, 224, 208, 0.65); margin: 0; } - .sub-all { - font-family: var(--font-sans); - font-size: var(--text-label-sm); - letter-spacing: var(--tracking-wide); - text-transform: uppercase; - color: var(--pigment-terracotta); - text-decoration: none; - border-bottom: none; - } - .sub-all:hover { opacity: 0.85; border-bottom: none; } + .hero-see-all { align-self: flex-start; } - .sub-divider { - height: 1px; - background: var(--surface-card-border); - border: none; - margin: var(--space-2) 0; + /* ── Latest from Fenja (unboxed) ──────────────────────────────── */ + .latest-article { + display: flex; + flex-direction: column; + gap: var(--space-3); + max-width: 56rem; } - - /* Latest from Fenja */ - .latest-dispatch { display: flex; flex-direction: column; gap: var(--space-3); } .latest-byline { display: flex; align-items: center; @@ -665,6 +482,7 @@ const members = getAllCabMembers(); .latest-byline-title { color: var(--on-surface-variant); } .latest-byline-time { color: var(--on-surface-muted); + font-size: var(--text-label-sm); letter-spacing: var(--tracking-wide); margin-left: auto; } @@ -680,120 +498,126 @@ const members = getAllCabMembers(); } .latest-title { font-family: var(--font-serif); - font-style: italic; font-weight: 400; - font-size: 1.25rem; - line-height: 1.3; + font-size: 1.625rem; + line-height: 1.25; color: var(--on-surface); margin: 0; + letter-spacing: var(--tracking-snug); } - .latest-excerpt { - margin: 0; + .latest-body { color: var(--on-surface); line-height: var(--leading-relaxed); + margin: 0; + font-size: var(--text-body-lg); } - .latest-read { - align-self: flex-start; - font-family: var(--font-sans); - font-size: var(--text-label-md); - font-weight: 500; - letter-spacing: var(--tracking-wide); - text-transform: uppercase; - color: var(--pigment-terracotta); - text-decoration: none; - border-bottom: none; - } - .latest-read:hover { opacity: 0.85; border-bottom: none; } - /* Roadmap rows */ - .roadmap-list { + /* ── Roadmap horizontal cards ─────────────────────────────────── */ + .roadmap-section { + display: flex; + flex-direction: column; + gap: var(--space-4); + } + .roadmap-grid { list-style: none; padding: 0; margin: 0; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: var(--space-4); + } + .roadmap-card { + background: var(--surface-card); + border: 0.5px solid var(--surface-card-border); + border-radius: var(--radius-md); + padding: var(--space-5); display: flex; flex-direction: column; + gap: var(--space-3); + min-height: 130px; } - .roadmap-row { - display: flex; - align-items: flex-start; - gap: var(--space-4); - padding: var(--space-3) 0; - } - .roadmap-row + .roadmap-row { border-top: 0.5px solid var(--surface-card-border); } - .status-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; - margin-top: 0.4em; } @keyframes breathe { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.4); opacity: 0.5; } } .status-dot.breathing { animation: breathe 2.4s ease-in-out infinite; } - - .roadmap-row-text { flex: 1; display: flex; flex-direction: column; gap: var(--space-1); } - .roadmap-row-title { margin: 0; font-weight: 500; color: var(--on-surface); } - .roadmap-row-blurb { + .roadmap-card-text { display: flex; flex-direction: column; gap: var(--space-1); } + .roadmap-card-title { + font-family: var(--font-serif); + font-weight: 400; + font-size: 1.0625rem; + line-height: 1.3; + color: var(--on-surface); + margin: 0; + } + .roadmap-card-blurb { color: var(--on-surface-muted); + font-family: var(--font-sans); + font-size: var(--text-label-sm); letter-spacing: var(--tracking-wide); text-transform: uppercase; margin: 0; } - /* ── Members strip ────────────────────────────────────────────── */ - .members-card { - background: var(--surface-card); - border: 0.5px solid var(--surface-card-border); - border-radius: var(--radius-lg); - padding: var(--space-6); + /* ── Council cards — larger, with company ─────────────────────── */ + .council-section { display: flex; flex-direction: column; - gap: var(--space-4); + gap: var(--space-5); } - .members-head-row { - display: flex; - justify-content: space-between; - align-items: baseline; - gap: var(--space-3); - } - - .members-strip { + .council-grid { list-style: none; padding: 0; margin: 0; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: var(--space-4); + } + .council-card { + background: var(--surface-card); + border: 0.5px solid var(--surface-card-border); + border-radius: var(--radius-lg); + padding: var(--space-6) var(--space-6) var(--space-7); display: flex; - flex-wrap: wrap; - gap: var(--space-3); - } - .member-pill { - display: inline-flex; align-items: center; - gap: var(--space-3); - padding: var(--space-2) var(--space-4) var(--space-2) var(--space-2); - background: var(--surface); - border-radius: var(--radius-full); + gap: var(--space-5); } - .member-pill-text { display: inline-flex; flex-direction: column; line-height: 1.2; min-width: 0; } - .member-pill-name { font-size: var(--text-body-sm); color: var(--on-surface); } - .member-pill-title { - color: var(--on-surface-muted); + .council-card-text { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; + } + .council-card-name { + font-family: var(--font-serif); + font-weight: 400; + font-size: 1.125rem; + line-height: 1.2; + color: var(--on-surface); + } + .council-card-title { + font-family: var(--font-sans); + font-size: var(--text-body-sm); + color: var(--on-surface-variant); + } + .council-card-org { + font-family: var(--font-sans); + font-size: var(--text-label-sm); letter-spacing: var(--tracking-wide); - text-transform: uppercase; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 14rem; + color: var(--on-surface-muted); } - /* ── Responsive: hero column collapse ─────────────────────────── */ - @media (max-width: 720px) { - .events-hero-body { grid-template-columns: 1fr; } - .events-hero-body::after { display: none; } - .events-list-row { grid-template-columns: 50px 1fr; } - .events-list-action-form, - .events-list-row > a { grid-column: 1 / -1; justify-self: start; padding-top: var(--space-2); } + /* ── Responsive ───────────────────────────────────────────────── */ + @media (max-width: 880px) { + .roadmap-grid { grid-template-columns: 1fr; } + .hero-body { grid-template-columns: 1fr; } + .hero-body::after { display: none; } + .hero-detail { padding-left: 0; } }