Commit efc02813 by PLN (Algolia)

fix(cosmicfest): link the lineup out, and stop the RSVP line going stale

Two gaps between the live page and the canonical invite text.

1. The lineup named the three acts but linked nowhere. PLN's invite carries a
   destination for each, so the page now sends people to the work:
     ParVagues  -> soundcloud.com/parvagues
     Nesta Flex -> soundcloud.com/antoine-renaudet
     shipow     -> instagram.com/shipow
   The anchor takes `color: inherit` so the existing odd/even cyan/magenta
   :hover rules on .lineupName stay in charge; the underline is a 30% mix that
   solidifies on hover/focus-visible rather than a permanent blue link.

2. The RSVP paragraph read "une réponse avant le 1er août, ce serait top."
   True when written, silently wrong since 2 August, and actively confusing
   today — 15 August, with arrivals open and jour J six days out. A page whose
   copy expires is the same failure as a countdown to a date that has passed.

   So it reads the clock instead of a frozen sentence. Four phases off the
   existing JOUR_J plus three new date constants: before the deadline, after it
   but pre-arrivals, during the 15-23 August window, and after — which hands
   over to the v3.42Hz teaser from the invite.

   Rendered null on the server and filled in an effect, because the phase
   depends on the viewer's clock and a server/client disagreement is a
   hydration mismatch. The invariant "pas de billetterie / Riders, come to the
   storm" line stays outside the component, so the paragraph still reads
   correctly if JS never arrives.

Every other fact on the page was checked against the canonical invite and
already matched: 20-23 August, arrivals from the 15th, Labenne-Océan, jour J
the 22nd, 10 indoor places, tents, van, 16 capacity, +1 welcome, both stations,
the 15-minute Bayonne->Labenne train, the taxis, and the packing list.

Verified: clean build, all three hrefs present in the prerendered HTML, and
zero occurrences of "1er août" in the server output.
parent 3e2d2cd3
......@@ -11,11 +11,32 @@ const sans = Archivo({ subsets: ['latin'], axes: ['wdth'], display: 'swap', vari
const mono = Martian_Mono({ subsets: ['latin'], display: 'swap', variable: '--font-cf-mono' });
const JOUR_J = '2026-08-22T18:00:00+02:00';
const RSVP_DEADLINE = '2026-08-01T23:59:59+02:00';
const ARRIVALS_OPEN = '2026-08-15T00:00:00+02:00';
const FEST_END = '2026-08-23T23:59:59+02:00';
const LINEUP = [
{ name: 'ParVagues', real: 'PLN', role: 'live coding · breakbeat → techno nujazz', emoji: '💻' },
{ name: 'Nesta Flex', real: 'Antoine', role: 'dj set', emoji: '🎛️' },
{ name: 'shipow', real: 'Kévin', role: 'projection visuelle', emoji: '🖼️' },
{
name: 'ParVagues',
real: 'PLN',
role: 'live coding · breakbeat → techno nujazz',
emoji: '💻',
url: 'https://soundcloud.com/parvagues',
},
{
name: 'Nesta Flex',
real: 'Antoine',
role: 'dj set',
emoji: '🎛️',
url: 'https://soundcloud.com/antoine-renaudet',
},
{
name: 'shipow',
real: 'Kévin',
role: 'projection visuelle',
emoji: '🖼️',
url: 'https://www.instagram.com/shipow/',
},
];
const PROGRAMME = [
......@@ -49,6 +70,59 @@ const MAILTO =
'&body=' +
encodeURIComponent('salut ! je viens.\n\nmon côté cosmique : ...\n+1 : oui / non\narrivée prévue : ...\n');
/**
* The RSVP line used to hardcode "réponds avant le 1er août" — correct when it was
* written, quietly wrong from 2 August onward, and actively confusing during the
* festival itself. It now reads the clock instead of a frozen sentence.
*
* Rendered empty on the server and filled in an effect: the phase depends on the
* viewer's `now`, and any server/client disagreement would be a hydration mismatch.
* The invariant "pas de billetterie" line sits outside, so the page never looks
* broken if JS never arrives.
*/
function RsvpNote() {
const [phase, setPhase] = useState(null);
useEffect(() => {
const now = Date.now();
if (now < Date.parse(RSVP_DEADLINE)) setPhase('early');
else if (now < Date.parse(ARRIVALS_OPEN)) setPhase('late');
else if (now <= Date.parse(FEST_END)) setPhase('now');
else setPhase('over');
}, []);
if (!phase) return null;
if (phase === 'early') {
return (
<>
Une réponse avant le <b>1er août</b>, ce serait top.{' '}
</>
);
}
if (phase === 'late') {
return (
<>
La date de réponse est passée <b>écris-moi quand même</b>.{' '}
</>
);
}
if (phase === 'now') {
return (
<>
Ça y est : les arrivées sont ouvertes depuis le <b>15 août</b>, jour J le{' '}
<b>22</b>. Écris-moi, même à la dernière minute.{' '}
</>
);
}
return (
<>
La v2.67Hz est passée. Rendez-vous pour la <b>cosmicfest_v3.42Hz</b>, plus sauvage
et entièrement décohérée.{' '}
</>
);
}
export default function CosmicFest() {
const [modal, setModal] = useState(null); // { src, alt }
const rootRef = useRef(null);
......@@ -162,7 +236,19 @@ export default function CosmicFest() {
{LINEUP.map((act) => (
<li key={act.name} className={styles.lineupRow}>
<span className={styles.lineupName}>
{act.name} <span className={styles.lineupReal}>~ {act.real}</span>
{act.url ? (
<a
className={styles.lineupLink}
href={act.url}
target="_blank"
rel="noopener noreferrer"
>
{act.name}
</a>
) : (
act.name
)}{' '}
<span className={styles.lineupReal}>~ {act.real}</span>
</span>
<span className={styles.lineupRole}>
{act.emoji} {act.role}
......@@ -316,8 +402,9 @@ export default function CosmicFest() {
réponds à l&rsquo;invitation
</a>
<p className={styles.waitlistMessage}>
Une réponse avant le <b>1er août</b>, ce serait top. Pas de billetterie : cosmicfest, c&rsquo;est
sur invitation. <b>Riders, come to the storm.</b>
<RsvpNote />
Pas de billetterie : cosmicfest, c&rsquo;est sur invitation.{' '}
<b>Riders, come to the storm.</b>
</p>
</section>
</main>
......
......@@ -258,6 +258,19 @@
}
.lineupRow:nth-child(odd) .lineupName:hover { color: var(--cf-cyan); }
.lineupRow:nth-child(even) .lineupName:hover { color: var(--cf-magenta); }
/* The artist name links out to where their latest work actually lives.
`inherit` keeps the odd/even cyan/magenta hover rules above in charge. */
.lineupLink {
color: inherit;
text-decoration: underline;
text-decoration-thickness: 1px;
text-decoration-color: color-mix(in oklch, currentColor 30%, transparent);
text-underline-offset: 0.14em;
transition: text-decoration-color 0.2s ease;
}
.lineupLink:hover,
.lineupLink:focus-visible { text-decoration-color: currentColor; }
.lineupReal { color: var(--cf-muted); font-weight: 500; font-size: 0.5em; font-stretch: 100%; }
.lineupRole {
font-family: var(--cf-font-mono);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment