Skip to content
2 changes: 1 addition & 1 deletion src/lib/Footer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<div class="footer primary-bg">
<div class="footer-content">
<div class="footer-left">
<p>&copy; 2025 DDD South West | <a href="/privacy-policy">Privacy Policy</a></p>
<p>&copy; 2026 DDD South West | <a href="/privacy-policy">Privacy Policy</a></p>
</div>
<div class="footer-right">
<a href="https://bsky.app/profile/dddsouthwest.com" target="_blank">
Expand Down
9 changes: 7 additions & 2 deletions src/lib/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import Navbar from '$lib/Navbar.svelte';
// import Carousel from '$lib/Carousel.svelte';
import { pageTitle, navExpanded } from '../stores.js';
import { currentUpdate } from './latestUpdate.js';

function toggleNav() {
if ($navExpanded === true) {
Expand Down Expand Up @@ -29,7 +30,11 @@
</a>
<div>
<h1>DDD South West 2026</h1>
<p>Coming soon...</p>
{#if currentUpdate}
<p>
{@html currentUpdate}
</p>
{/if}
</div>
<Navbar />
</div>
Expand All @@ -38,7 +43,7 @@
<div class="bg">
<div class="header">
<a href="/" class="logo-link" onclick={toggleNav}>
<img src="../images/dddsw-logo-2025.png" alt="The DDD South West logo" class="logo" />
<img src="images/the_mighty_cow_white.svg" alt="The DDD South West cow" class="logo" />
</a>
<h1>{$pageTitle}</h1>
<Navbar />
Expand Down
114 changes: 0 additions & 114 deletions src/lib/LatestUpdate.svelte

This file was deleted.

2 changes: 1 addition & 1 deletion src/lib/Navbar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
let navItems = [
{ text: 'Home', href: '/', hidden: false },
{ text: 'About', href: '/about', hidden: false },
{ text: 'Sponsorship', href: '/sponsorship', hidden: true },
{ text: 'Sponsorship', href: '/sponsorship', hidden: false },
{ text: 'Event Venue', href: '/venue', hidden: true },
{ text: 'Room map', href: '/venue-layout', hidden: true },
{ text: 'New Speakers Workshop', href: '/new-speakers-workshop', hidden: true },
Expand Down
78 changes: 78 additions & 0 deletions src/lib/Timeline.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<script lang="ts">
import { get, Milestone, MilestoneDetails } from './milestones';

type Item = {
description: string;
milestone: MilestoneDetails;
};

const timelineItems: Item[] = [
{ description: 'Call for speakers opens', milestone: get(Milestone.OpenCallForSpeakers)! },
{ description: 'Call for speakers closes', milestone: get(Milestone.CloseCallForSpeakers)! },
{ description: 'Session voting opens', milestone: get(Milestone.OpenSessionVoting)! },
{ description: 'Session voting closes', milestone: get(Milestone.CloseSessionVoting)! },
{
description: 'Schedule announced and ticket registration opens',
milestone: get(Milestone.AnnounceScheduleAndOpenTicketRegistration)!
},
{ description: 'Day of the event!', milestone: get(Milestone.TheActualEventDay)! }
];
</script>

<h2>Timeline</h2>
<div class="timeline text-center">
{#each timelineItems as item}
<span class="timeline-item">
<span class="material-symbols-outlined">
{#if item.milestone.hasHappened}
check_circle
{:else}
circle
{/if}
</span>
<p>{item.milestone.formattedDate}</p>
<p>{item.description}</p>
</span>
{/each}
</div>
<sub class="sub-highlight"
>We'll try our best to keep to these dates but please be patient - DDDSW is entirely volunteer
driven ❤️
</sub>

<style>
.timeline {
display: flex;
flex-direction: column;
gap: 20px;
}

.timeline p {
font-size: 0.75rem;
margin: 0;
font-weight: bold;
}

.timeline-item p:first-of-type {
border-bottom: #cdcdcd 2px dotted;
margin-bottom: 5px;
font-weight: normal;
}

.timeline-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1 1 0;
}

.sub-highlight {
font-style: italic;
}

@media (min-width: 576px) {
.timeline {
flex-direction: row;
}
}
</style>
52 changes: 52 additions & 0 deletions src/lib/latestUpdate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Milestone, get } from '$lib/milestones';

const linkedInIcon = `<a href="https://www.linkedin.com/company/ddd-south-west/" target="_blank">
<i class="fa-brands fa-linkedin fa-lg" title="DDD South West LinkedIn" aria-hidden="true"></i>
<span class="fa-sr-only">DDD South West LinkedIn</span>
</a>`;

const sessionizeUrl = 'https://sessionize.com/ddd-south-west-2026/';
const votingUrl = 'https://buff.ly/4hzncRk';
const ticketTailorUrl = '...';
const pocketdddUrl = 'https://pocket2026.dddsouthwest.com';

export let currentUpdate: string | undefined;

if (get(Milestone.StartPlanning)?.hasHappened)
currentUpdate = `DDD South West 2026 planning has begun! More info coming soon 😎`;

if (get(Milestone.SetADate)?.hasHappened)
currentUpdate = `DDD South West is coming... save the date! See you on the <strong class="emphasis">${get(Milestone.TheActualEventDay)?.formattedDate}</strong>`;

if (get(Milestone.OpenCallForSpeakers)?.willBeHappeningSoon())
currentUpdate = `Watch this space.... call for speakers is opening on ${get(Milestone.OpenCallForSpeakers)!.formattedDate}.`;

if (get(Milestone.OpenCallForSpeakers)?.hasHappened)
currentUpdate = `Call for speakers is <strong class="emphasis">open until ${get(Milestone.CloseCallForSpeakers)!.formattedDate}</strong>.
We are a friendly developers conference and welcome papers from speakers both old and new.
So whether you have an established talk you'd like to bring to Bristol or an idea you'd like help developing,
<a href="${sessionizeUrl}">submit your talk now</a>.`;

if (get(Milestone.CloseCallForSpeakers)?.hasHappened)
currentUpdate = `Call for speakers is now closed. Session voting will be opening soon, watch this space or follow us on ${linkedInIcon}`;

if (get(Milestone.OpenSessionVoting)?.hasHappened)
currentUpdate = `Session voting is now open! Take a look and <a href="${votingUrl}">vote for your favourites here</a>.<br>Voting will close on ${get(Milestone.CloseSessionVoting)!.formattedDate}.`;

if (get(Milestone.CloseSessionVoting)?.hasHappened)
currentUpdate = `Session voting has closed, hold tight - ticket registration will be opening on ${get(Milestone.AnnounceScheduleAndOpenTicketRegistration)!.formattedDate}`;

if (get(Milestone.AnnounceScheduleAndOpenTicketRegistration)?.hasHappened)
currentUpdate = `Speakers have been announced and the schedule is live! <a href="/schedule">Check full schedule details here</a>. Get your tickets from <a href="${ticketTailorUrl}">Ticket Tailor</a>.`;

if (get(Milestone.SoldOut)?.hasHappened)
currentUpdate = `The day is fast approaching, we're fully sold out but you can still join the waitlist on <a href="${ticketTailorUrl}">Ticket Tailor</a>.`;

if (get(Milestone.FoundMoreTickets)?.hasHappened)
currentUpdate = `Good news - some tickets have become available! Go to <a href="${ticketTailorUrl}">Ticket Tailor</a> to grab one before they go.`;

if (get(Milestone.TheActualEventDay)?.hasHappened)
currentUpdate = `Today is the day! See you at Engine Shed and be sure to use <a href="${pocketdddUrl}">Pocket DDD</a> to give feedback to our wonderful speakers`;

if (get(Milestone.ConkedOut)?.hasHappened)
currentUpdate = `Thank you to everyone for another great DDD South West. See you in ${get(Milestone.ConkedOut)!.targetDate!.getFullYear() + 1} 😉`;
68 changes: 68 additions & 0 deletions src/lib/milestones.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
enum Status {
Done,
NotYet,
JustYouWait
}

export enum Milestone {
StartPlanning,
SetADate,
OpenCallForSpeakers,
CloseCallForSpeakers,
OpenSessionVoting,
CloseSessionVoting,
AnnounceScheduleAndOpenTicketRegistration,
SoldOut,
FoundMoreTickets,
TheActualEventDay,
ConkedOut
}

export class MilestoneDetails {
hasHappened: boolean;
formattedDate?: string;
targetDate?: Date;

constructor(status: Status.Done | Status.NotYet);
constructor(status: Status, date: string);
constructor(status: Status, date?: string) {
if (date) {
this.targetDate = new Date(date);

const formatter = new Intl.DateTimeFormat(undefined, { dateStyle: 'long' });
this.formattedDate = formatter.format(this.targetDate);
}
if (status === Status.JustYouWait) this.hasHappened = this.targetDate!.valueOf() < Date.now();
else this.hasHappened = status === Status.Done;
}

willBeHappeningSoon(): boolean {
if (!this.targetDate || this.hasHappened) return false;

const now = new Date();
const oneMonthFromNowTimestamp = now.setMonth(now.getMonth() + 1);

return this.targetDate.getTime() < oneMonthFromNowTimestamp;
}
}

export function get(doneThing: Milestone): MilestoneDetails | undefined {
return milestones.get(doneThing);
}

const milestones = new Map([
[Milestone.StartPlanning, new MilestoneDetails(Status.Done)],
[Milestone.SetADate, new MilestoneDetails(Status.Done)],
[Milestone.OpenCallForSpeakers, new MilestoneDetails(Status.NotYet, '2026-01-31')],
[Milestone.CloseCallForSpeakers, new MilestoneDetails(Status.JustYouWait, '2026-03-07')],
[Milestone.OpenSessionVoting, new MilestoneDetails(Status.NotYet, '2026-03-14')],
[Milestone.CloseSessionVoting, new MilestoneDetails(Status.JustYouWait, '2026-03-28')],
[
Milestone.AnnounceScheduleAndOpenTicketRegistration,
new MilestoneDetails(Status.NotYet, '2026-04-18')
],
[Milestone.SoldOut, new MilestoneDetails(Status.NotYet)],
[Milestone.FoundMoreTickets, new MilestoneDetails(Status.NotYet)],
[Milestone.TheActualEventDay, new MilestoneDetails(Status.JustYouWait, '2026-05-16')],
[Milestone.ConkedOut, new MilestoneDetails(Status.JustYouWait, '2026-05-17')]
]);
Loading
Loading