Full Code of Readify/DevEvents for AI

main b09bbab9d2d8 cached
51 files
108.4 KB
33.4k tokens
36 symbols
1 requests
Download .txt
Repository: Readify/DevEvents
Branch: main
Commit: b09bbab9d2d8
Files: 51
Total size: 108.4 KB

Directory structure:
gitextract_z1vav1gl/

├── .github/
│   └── workflows/
│       ├── deploy.yml
│       └── pr-preview.yml
├── CLAUDE.md
├── CONTRIBUTING.md
├── FAQ.md
├── LICENSE.md
├── Meetups/
│   ├── ACT.md
│   ├── NSW.md
│   ├── NT.md
│   ├── QLD.md
│   ├── SA.md
│   ├── TAS.md
│   ├── VIC.md
│   └── WA.md
├── Past Events/
│   ├── 2016.md
│   ├── 2017.md
│   ├── 2018.md
│   ├── 2019.md
│   ├── 2020.md
│   ├── 2022.md
│   ├── 2024.md
│   ├── 2025.md
│   └── OTHER.md
├── README.md
└── website/
    ├── .gitignore
    ├── astro.config.mjs
    ├── package.json
    ├── public/
    │   └── robots.txt
    ├── src/
    │   ├── components/
    │   │   ├── EventCard.astro
    │   │   ├── EventTable.astro
    │   │   ├── FilterIsland.tsx
    │   │   ├── Footer.astro
    │   │   ├── Nav.astro
    │   │   └── UpcomingEventsIsland.tsx
    │   ├── data/
    │   │   ├── normalize-state.ts
    │   │   ├── parse-date.ts
    │   │   ├── parse-events.ts
    │   │   ├── parse-meetups.ts
    │   │   └── types.ts
    │   ├── env.d.ts
    │   ├── layouts/
    │   │   └── Base.astro
    │   ├── pages/
    │   │   ├── 404.astro
    │   │   ├── about.astro
    │   │   ├── events/
    │   │   │   ├── index.astro
    │   │   │   └── past/
    │   │   │       ├── [year].astro
    │   │   │       └── index.astro
    │   │   ├── index.astro
    │   │   └── meetups/
    │   │       ├── [state].astro
    │   │       └── index.astro
    │   └── styles/
    │       └── global.css
    └── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/deploy.yml
================================================
name: Build and Deploy

on:
  push:
    branches: [main]
    paths:
      - 'README.md'
      - 'Past Events/**'
      - 'Meetups/**'
      - 'website/**'
  workflow_dispatch:

permissions:
  contents: read

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6.0.2

      - uses: actions/setup-node@v6.3.0
        with:
          node-version: '22'
          cache: 'npm'
          cache-dependency-path: website/package-lock.json

      - name: Install dependencies
        run: npm ci
        working-directory: website

      - name: Build site
        run: npm run build
        working-directory: website

      - name: Deploy to Cloudflare Pages
        uses: cloudflare/wrangler-action@v3.1.0
        with:
          wranglerVersion: '3.90.0'
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy dist --project-name=devevents --branch=main
          workingDirectory: website


================================================
FILE: .github/workflows/pr-preview.yml
================================================
name: PR Build Check

on:
  pull_request:
    branches: [main]
    paths:
      - 'README.md'
      - 'Past Events/**'
      - 'Meetups/**'
      - 'website/**'

jobs:
  build-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6.0.2

      - uses: actions/setup-node@v6.3.0
        with:
          node-version: '25'
          cache: 'npm'
          cache-dependency-path: website/package-lock.json

      - name: Install dependencies
        run: npm ci
        working-directory: website

      - name: Build site
        run: npm run build
        working-directory: website


================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What This Is

A community-maintained list of Australian developer events and meetups. The dataset lives in Markdown files. A static website at `devevents.io` is built from these files using Astro (source in `website/`).

## Website Development

```bash
cd website
npm install
npm run dev      # dev server at localhost:4321
npm run build    # build to website/dist/
npm run preview  # preview the build
```

The website is an Astro 4.x static site with Preact islands for filtering. Data is parsed at build time from the Markdown files. The build runs automatically via GitHub Actions on push to `main` when data files or `website/**` change, deploying to Cloudflare Pages.

## Structure

- `README.md` — upcoming events table (the primary file for new event additions)
- `Past Events/<year>.md` — events archived by year
- `Meetups/<state>.md` — recurring meetups by Australian state (ACT, NSW, NT, QLD, SA, TAS, VIC, WA)
- `CONTRIBUTING.md` — contribution rules
- `FAQ.md` — project FAQ

## Data Format Rules

When editing the events table, follow these conventions exactly (required for future parsing):

- Dates: `dd-Mmm-yyyy` format (e.g., `01-Jan-2026`). Use `TBC` if unknown.
- States: `VIC`, `NSW`, `ACT`, `WA`, `SA`, `NT`, `QLD`, `ALL`, `TAS`, `OTH`
- Events go in **chronological order** within a section
- No extra spaces or formatting characters in table cells
- Only Australian-based events of interest to the software development community
- Links must be verified before adding

## Upcoming vs Past Events

- New events belong in the `README.md` upcoming table
- When a year ends, its events move to `Past Events/<year>.md` (see the archive commit `fb51c7e` for the pattern)
- Only active meetup pages are listed; add a link to `README.md` when adding a new state meetup file


================================================
FILE: CONTRIBUTING.md
================================================

# Australian Developer Events

## Contributing

You want to help? Great!! Here's what you need to know.

### How to contribute

Contributing to DevEvents is very easy with the GitHub markdown editor; you can do it all from your browser. (You are also welcome to use a standard PR process if you would prefer.)

### To submit a change using GitHub markdown editor, do the following

1. Browse to [README.md](README.md), the actual file behind it all
1. Click the [_Edit this file_ pencil icon](README.md)
1. Make your changes, respecting the rules below please
1. Use the _Preview Changes_ tab at the top to make sure everything still looks right
1. Scroll to the _Propose file change_ section at the bottom
1. Supply a basic description of your change in the first field (`Add Super Awesome Conf 2019`)
1. Hit the _Propose file change_ button
1. After navigating to the next page, hit the _Create pull request_ button

### For content

- We are only interested in Australian based events, and no earlier than 2016
- The event should be of interest to the dev community (specialist and related topics are fine e.g. AI and agile)
- Please add events in chronological order within the relevant year
- Check any links you are adding are correct
- Ensure dates are in dd-mmm-yyyy format (in case we want to parse this data later). For example, `01-Jan-2019`. Use TBC if the date is unconfirmed or unknown.
- Ensure no additional spaces or formatting characters are present (this will make parsing easier later)
- Ensure states are specified as VIC, NSW, ACT, WA, SA, NT, QLD, ALL, TAS, OTH (Other)

### For Meetups

- Currently only active meetup pages are listed on the main page
- If you are adding meetups to a new state page, ensure you add the page link to the main events page


================================================
FILE: FAQ.md
================================================

# Australian Developer Events

## Frequently Asked Questions - FAQ

### My event is not listed/wrongly classified/there is a mistake

No problem - please see [**How do I Contribute**](CONTRIBUTING.md) for details of how you can fix this, or add an issue and we'll look into it.

If an event is not listed, it might be that the event isn't sufficiently targeted at developers. If you disagree, open an issue and state your case. We're happy to find out more.

### What type of events will get listed

We're looking for events that match the following basic criteria:

- _It's an Australian based event, run anytime from 2016 onwards_
- _It's of interest to the software development community_

It doesn't matter if it's free or not, and we don't care if it's an in-person event or a virtual one. As long as it meets the criteria, we're happy!

### This should be done using RSS/ATOM/Microformat/JSON/Blockchain etc

This resource is only as good as people's contributions so we want to make it as easy as possible for people to make changes. Markdown in conjunction with GitHub markdown editor makes it very easy for anyone to modify the content, even without tooling. Feel free to build something cool based on this data.

### I am not technical - how can I contribute to this

Please [sign up on github](https://github.com/join) and follow the easy steps in the [**Contribution Guide**](CONTRIBUTING.md). You can also add an issue by clicking on the issue tab and we will get to it when we can.

### What is the license for this project

This repository is licensed under [**Creative Commons - CC0**](LICENSE.md).


================================================
FILE: LICENSE.md
================================================
# Creative Commons

## CC0 1.0 Universal

CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER.

### Statement of Purpose

The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").

Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.

For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.

1. __Copyright and Related Rights.__ A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:

    i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;

    ii. moral rights retained by the original author(s) and/or performer(s);

    iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;

    iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;

    v. rights protecting the extraction, dissemination, use and reuse of data in a Work;

    vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and

    vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.

2. __Waiver.__ To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.

3. __Public License Fallback.__ Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.

4. __Limitations and Disclaimers.__

    a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.

    b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.

    c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.

    d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.


================================================
FILE: Meetups/ACT.md
================================================

# Australian Developer Events

## Australian Capital Territory Meetups

| Event Name | Location | Host | Frequency | Tags |
| ---------- | -------- | ---- | --------- | ---- |
|  |  |  |  |  |


================================================
FILE: Meetups/NSW.md
================================================

# Australian Developer Events

## New South Wales Meetups

| Event Name | Location | Host | Frequency | Tags |
| ---------- | -------- | ---- | --------- | ---- |
|  |  |  |  |  |


================================================
FILE: Meetups/NT.md
================================================

# Australian Developer Events

## Northern Territory Meetups

| Event Name | Location | Host | Frequency | Tags |
| ---------- | -------- | ---- | --------- | ---- |
|  |  |  |  |  |


================================================
FILE: Meetups/QLD.md
================================================

# Australian Developer Events

## Queensland Meetups

| Event Name | Location | Host | Frequency | Tags |
| ---------- | -------- | ---- | --------- | ---- |
| [Microservices Matters](https://skillsmatter.com/groups/11075-microservices-matters) | Virtual | Skills Matter | Monthly | microservices |


================================================
FILE: Meetups/SA.md
================================================

# Australian Developer Events

## South Australian Meetups

| Event Name | Location | Host | Frequency | Tags |
| ---------- | -------- | ---- | --------- | ---- |
| [Adelaide .NET User Group](https://www.adnug.net/) | UniSA City West Campus / Virtual | David Gardiner | Monthly  | .NET |
| [Adelaide Data & Analytics User Group](https://www.eventbrite.com.au/o/adelaide-data-amp-analytics-user-group-938077899) | Microsoft Adelaide | LobsterPot Solutions | Monthly | SQL, Data, BI |
| [Front-End Developers Adelaide](https://www.meetup.com/en-AU/Front-End-Developers-Adelaide-FEDA/) | Belgian Beer Cafe | - | Bimonthly | Front end, Web |
| [Women in Technology SA](https://www.eventbrite.com.au/o/women-in-technology-sa-30256736884) | Whitmore Hotel / Virtual | - | Monthly | |
| [Python Adelaide](https://www.meetup.com/pythonadelaide/) | UnA North Terrace Campus / Virtual | - | Monthly | Python|
| [Junior Developers Adelaide](https://www.meetup.com/junior-developers-adelaide/)| Belgian Beer Cafe | - | Monthly | Junior |


================================================
FILE: Meetups/TAS.md
================================================

# Australian Developer Events

## Tasmanian Meetups

| Event Name | Location | Host | Frequency | Tags |
| ---------- | -------- | ---- | --------- | ---- |
|  |  |  |  |  |


================================================
FILE: Meetups/VIC.md
================================================

# Australian Developer Events

# Victorian Meetups

| Event Name | Location | Host | Frequency | Tags |
| ---------- | -------- | ---- | --------- | ---- |
| [Angular Melbourne](https://www.meetup.com/Angular-Melbourne/) | 6/180 Flinders St, Melbourne | Netwealth | Quarterly | Angular |
| [AWS Programming and Tools](https://www.meetup.com/Melbourne-AWS-Programming-and-Tools-Meetup/) | Various | Various | Monthly | AWS |
| [DDD By Night](https://www.meetup.com/DDD-Melbourne-By-Night/) | 452 Flinders Street, Melbourne | Mantel Group | Bi-Monthly | Various |
| [GDG Melbourne](https://www.meetup.com/gdg-melbourne/) | 452 Flinders Street, Melbourne | Mantel Group | Monthly | GCP |
| [Golang Melbourne](https://www.meetup.com/golang-mel/) | 452 Flinders Street, Melbourne | Mantel Group | Monthly | Golang |
| [Her Tech Circle](https://www.hertechcircle.org/events) | Various | Various | Monthly | Women in Tech |
| [IRL by Amber](https://lu.ma/SparksXGenesis?k=c) | Various | Various | Monthly | "Australia's top minds & startups" |
| [Machine Learning & AI](https://www.meetup.com/Machine-Learning-AI-Meetup/) | 912 Collins Street, Melbourne | | Monthly | Machine Learning & AI |
| [MelbJS](http://melbjs.com/) | Level 2/29 Stewart Street, Richmond | Culture Amp | | JavaScript |
| [Melbourne MLOps Community](https://www.meetup.com/melbourne-mlops-community1/) | 452 Flinders St, Melbourne | Mantel Group | Bi-Monthly | MLOps |
| [React Melbourne](https://www.meetup.com/React-Melbourne) | 139 Gladstone St, South Melbourne | Kogan | Quarterly | React |
| [Ruby Melbourne](https://www.meetup.com/Ruby-On-Rails-Oceania-Melbourne/) | Boyd Community Hub, 207 City Rd, Southbank | Assembly Four, CultureAmp, HotDoc, Gleam.io | Monthly | Ruby |
| [Tech Leading Ladies](https://www.meetup.com/Tech-Leading-Ladies/) | 550 Bourke St, Melbourne | Zendesk | Monthly | Women in Tech Leadership |
| [VIC.NET](https://www.meetup.com/VIC-NET-Meetup/) | 5/4 Freshwater Place, Southbank | Microsoft | Bi-Monthly | .NET |
| [Women Coders](https://www.meetup.com/women-coders/) | Various | Various | Monthly | Technical Women |
| [Women in Cloud](https://www.meetup.com/women-in-cloud-meetup-group/) | Various | Various | Monthly | Women in Cloud Computing |

## Stale Meetups

Unfortunately, some Meetups are no longer running post-covid. If you are interested in running a meetup, or want to see these events back, please reach out via the links below.

| Event Name |
| ---------- |
| [ALT.NET](https://www.meetup.com/Melbourne-ALT-NET/) |
| [Junior Developers](https://www.meetup.com/Junior-Developers-Melbourne/) |
| [The Web Meetup](https://www.meetup.com/the-web) |


================================================
FILE: Meetups/WA.md
================================================

# Australian Developer Events

## Western Australian Meetups

| Event Name | Location | Host | Frequency | Tags |
| ---------- | -------- | ---- | --------- | ---- |
|  |  |  |  |  |


================================================
FILE: Past Events/2016.md
================================================

# Australian Developer Events

## Event Archive

### 2016

| Event Name | State | Date From | Date To | Tags |
| ---------- | ----- | --------- | ------- | ---- |
| [Unite](https://unite.unity.com/) | VIC | 23-Oct-2016 | 31-Oct-2016 | Unity |
| [MixInConf](http://mixinconf.com/) | WA | 28-Oct-2016 | 28-Oct-2016 | Design/Web |


================================================
FILE: Past Events/2017.md
================================================

# Australian Developer Events

## Event Archive

### 2017

| Event Name | State | Date From | Date To | Tags |
| ---------- | ----- | --------- | ------- | ---- |
| [SQL Saturday](http://sqlsaturday.com/) | NSW | 18-Feb-2017 | 18-Feb-2017 | SQL |
| [Ignite Australia](https://msftignite.com.au/) | QLD | 13-Feb-2017 | 14-Feb-2017 | Microsoft Technologies |
| [CrikeyCon](https://www.crikeycon.com/) | QLD | 20-Feb-2017 | 20-Feb-2017 | Security |
| [APIDays](http://au.apidays.io/) | VIC | 01-Mar-2017 | 01-Mar-2017 | API |
| [Australian Cyber Security](https://acsc2017.com.au/) | ACT | 14-Mar-2017 | 16-Mar-2017 | Security |
| [Edge of the Web](http://www.eotw.com.au/) | WA | 26-Mar-2017 | 27-Mar-2017 | Web |
| [WWW 2017](http://www2017.com.au/) | WA | 03-Apr-2017 | 07-Apr-2017 | Web |
| [CampJS](http://viii.campjs.com/) | VIC | 08-Apr-2017 | 08-Jul-2017 | JS/Web |
| [YoW! Brisbane](http://brisbane.yowconference.com.au/) | QLD | 12-Apr-2017 | 12-May-2017 | Agile, Lean, Product |
| [AWS Summit](https://aws.amazon.com/summits/) | NSW | 04-May-2017 | 04-Jun-2017 | AWS |
| [Australian Testing Days](https://www.australiantestingdays.com/) | VIC | 20-May-2017 | 21-May-2017 | Testing |
| [AusCERT](https://www.auscert.org.au/events/2017-May-23-16th-annual-auscert-cyber-security-conf) | QLD | 23-May-2017 | 26-May-2017 | Security |
| [SQL Saturday](http://sqlsaturday.com/) | QLD | 27-May-2017 | 27-May-2017 | SQL |
| [Global DevOps Bootcamp](http://globaldevopsbootcamp.com/) | Multi | 17-Jun-2017 | 17-Jun-2017 | Devops |
| [DDD Sydney]( http://2017.dddsydney.com.au/) | NSW | 15-Jul-2017 | 15-Jul-2017 | Various |
| [Devops days](https://www.devopsdays.org/) | VIC | 16-Jul-2017 | 16-Jul-2017 | Devops |
| [LAST Conf](https://www.lastconference.com/) | VIC | 29-Jul-2017 | 30-Jul-2017 | Agile, Lean, Product |
| [DDD Melbourne](https://www.dddmelbourne.com/) | VIC | 12-Aug-2017 | 12-Aug-2017 | Various |
| [Dev World](http://www.devworld.com.au/) | VIC | 28-Aug-2017 | 30-Aug-2017 | Various |
| [AWS DevDay Brisbane](https://aws.amazon.com/devday/australia/) | QLD | 05-Sep-2017 | 05-Sep-2017 | AWS |
| [AWS DevDay Melbourne](https://aws.amazon.com/devday/australia/) | VIC | 07-Sep-2017 | 05-Sep-2017 | AWS |
| [OWASP AppSec AU Conference](https://www.meetup.com/en-AU/Application-Security-OWASP-Melbourne/events/241082215/) | VIC | 09-Sep-2017 | 09-Sep-2017 | Security |
| [DDD Perth](https://dddperth.com/) | WA | 16-Sep-2017 | 16-Sep-2017 | Various |
| [NDC Sydney](https://ndcsydney.com/) | NSW | 17-Sep-2017 | 21-Sep-2017 | Various |
| [Yow Connected](http://connected.yowconference.com.au/) | VIC | 21-Sep-2017 | 22-Sep-2017 | Mobile |
| [Aus Scrum](http://scrum.com.au/2017/) | VIC | 21-Sep-2017 | 22-Sep-2017 | Scrum |
| [YoW! Data](http://data.yowconference.com.au/) | NSW | 22-Sep-2017 | 23-Sep-2017 | Data |
| [Devops days](https://www.devopsdays.org/) | WA | 14-Oct-2017 | 15-Oct-2017 | Devops |
| [Agile Encore](http://www.agileencore.com/) | VIC | 21-Oct-2017 | 21-Oct-2017 | Agile |
| [Ruxcon](https://ruxcon.org.au) | VIC | 21-Oct-2017 | 22-Oct-2017 | Security |
| [Pax](http://aus.paxsite.com/) | VIC | 27-Oct-2017 | 29-Oct-2017 | Games |
| [Rails Camp](https://rails.camp/#au_nov_2017) | VIC | 01-Nov-2017 | 02-Nov-2017 | Rails |
| [HealthHack](https://www.healthhack.com.au/) | Multi | 03-Nov-2017 | 04-Nov-2017 | Hackathon |
| [Agile tour Sydney](https://www.eventbrite.com.au/e/agile-tour-sydney-2017-tickets-29363059702) | VIC | 06-Nov-2017 | 06-Nov-2017 | Agile |
| [Open Stack Summit](https://www.openstack.org/summit/) | NSW | 06-Nov-2017 | 08-Nov-2017 | Infrastructure |
| [Latency Conf](https://www.latencyconf.io/) | WA | 16-Nov-2017 | 16-Nov-2017 | High performance apps |
| [Puppet Camp](https://puppet.com/community/events/camp/puppet-camp-melbourne-2017) | VIC | 17-Nov-2017 | 17-Nov-2017 | Puppet |
| [Global Day of Code Retreat](http://coderetreat.org/) | VIC | 18-Nov-2017 | 18-Nov-2017 | Various|
| [Write the docs](http://www.writethedocs.org/) | VIC | 24-Nov-2017 | 24-Nov-2017 | Technical Documentation |
| [YoW! Melbourne](http://melbourne.yowconference.com.au/) | VIC | 30-Nov-2017 | 01-Dec-2017 | Agile, Lean, Product |
| [BuzzConf](https://buzzconf.io/) | VIC | 01-Dec-2017 | 03-Dec-2017 | Future Tech |
| [DDD Brisbane](http://www.dddbrisbane.com/) | QLD | 02-Dec-2017 | 02-Dec-2017| Various |
| [YOW! Brisbane](http://brisbane.yowconference.com.au/) | QLD | 04-Dec-2017 | 05-Dec-2017 | Various |
| [YOW! Sydney](http://sydney.yowconference.com.au/) | NSW | 07-Dec-2017 | 08-Dec-2017 | Various |
| [TConf](https://tconf.io/) | VIC | 08-Dec-2017 | 08-Dec-2017 | Testing |


================================================
FILE: Past Events/2018.md
================================================

# Australian Developer Events

## Event Archive

### 2018

| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |
| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |
| [Container Camp](https://2018.container.camp/au/) | VIC | 24-May-2018 | 25-May-2018 | | | Containers |
| [Angular Conf](https://www.angularconf.com.au/) | VIC | 22-Jun-2018 | 22-Jun-2018 | | | Web |
| [Levels Conf](https://www.levelsconf.com/index.html) | VIC | 07-Jul-2018 | 07-Jul-2018 | | | Web |
| [DDD Melbourne](https://www.dddmelbourne.com/) | VIC | 15-Sep-2018 | | 27-Apr-2018 | 19-Jun-2018 | All the things |
| [NDC Sydney](http://www.ndcsydney.com/) | NSW | 17-Sep-2018 | 21-Sep-2018 | | | Various |
| [YOW! Connected](http://connected.yowconference.com.au/) | VIC | 17-Sep-2018 | 18-Sep-2018 |  |  | IOT |
| [Business Agility Conference](http://businessagility.yowconference.com.au/) | NSW | 24-Sep-2018 | 25-Sep-2018 | Feb-2018 | 31-May-2018 | Agile |
| [A11y Camp](https://a11ycamp.org.au/) | VIC | 17-Oct-2018 | 19-Oct-2018 | | | Accessibility |
| [AISA Australian Cyber Conference](https://cyberconference.com.au) | VIC | 09-Oct-2018 | 11-Oct-2018 | | | Security |
| [OWASP AppSec Day](https://appsecday.io/) | VIC | 19-Oct-2018 | 19-Oct-2018 | | | Security |
| [Test Bash Australia](https://www.ministryoftesting.com/2017/11/testbash-australia-throw-another-testbash-barbie/) | NSW | 19-Oct-2018 |  | | | Testing |
| [MeasureCamp Sydney](http://sydney.measurecamp.org/) | NSW | 20-Oct-2018 | | | | Analytics |
| [DevFest](https://www.gdgmelbourne.com/devfest-2018/) | VIC | 27-Oct-2018 | 27-Oct-2018 | | | Android |
| [Web Directions AI](http://www.webdirections.org/ai/) | NSW | 31-Oct-2018 | | | | AI |
| [Web Directions Culture](http://www.webdirections.org/culture/) | NSW | 31-Oct-2018 |  | | | Culture |
| [Web Directions Summit](http://www.webdirections.org/wds/) | NSW | 01-Nov-2018 | 02-Nov-2018 | 01-Dec-2017 | May 2018 | Web (front end/design) |
| [Latency Conf](https://www.latencyconf.io/) | WA | 15-Nov-2018 | 15-Nov-2018 |   |  27-Jul-2018 | High Performance Apps |
| [TConf 2018](https://www.eventbrite.com.au/e/tconf-2018-melbournes-own-software-testing-conference-tickets-39909638804?aff=es2) | VIC | 23-Nov-2018 | | | | Testing |
| [GDG Cloud](https://gdgcloud.melbourne/) | VIC | 24-Nov-2018 | 24-Nov-2018 | | | Google Cloud |
| [YOW! Sydney](http://sydney.yowconference.com.au/) | NSW | 29-Nov-2018 | 30-Nov-2018 |  |  | Various |
| [DDD Brisbane](http://dddbrisbane.com/) | QLD | 01-Dec-2018 |  | TBC | TBC | All the things |
| [YOW! Brisbane](http://brisbane.yowconference.com.au/) | QLD | 03-Dec-2018 | 04-Dec-2018 |  |  | Various |
| [YOW! Melbourne](http://melbourne.yowconference.com.au/) | VIC | 06-Dec-2018 | 07-Dec-2018 |  |  | Various |


================================================
FILE: Past Events/2019.md
================================================

# Australian Developer Events

## Event Archive

### 2019

| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |
| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |
| [DataWorks Summit](https://10times.com/dataworks-summit-melbourne) | VIC | 06-Feb-2019 | 06-Feb-2019 |  |  | Data, AI Machine Learning |
| [RubyConf](https://www.rubyconf.org.au/2019) | VIC | 07-Feb-2019 | 09-Feb-2019 | 03-Sep-2018 | 15-Oct-2018 | Ruby |
| [Microservices, Containers & Serverless Day](https://1point21gws.com/microservices/melbourne/) | VIC | 08-Feb-2019 | 08-Feb-2019 |  |  | Microservices |
| [Microsoft Ignite Tour](https://www.microsoft.com/en-au/ignite-the-tour/sydney) | NSW | 13-Feb-2019 | 14-Feb-2019 |  |  | Cloud Infrastructure |
| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/163) | WA | 23-Feb-2019 | 23-Feb-2019 |  |  | Tech Talks |
| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/122) | VIC | 02-Mar-2019 | 02-Mar-2019 |  |  | Tech Talks |
| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/89) | NSW | 02-Mar-2019 | 02-Mar-2019 |  |  | Tech Talks |
| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/199) | QLD | 02-Mar-2019 | 02-Mar-2019 |  |  | Tech Talks |
| [BSides Canberra](https://www.bsidescbr.com.au/) | ACT | 15-Mar-2019 | 16-Mar-2019 | 01-Nov-2018 | 31-Dec-2018 | Security |
| [DevOps Talks Conference](https://devopstalks.com/au/devops.html/) | VIC | 19-Mar-2019 | 22-Mar-2019 |  |  | DevOps |
| [CrikeyCon](https://www.crikeycon.com/) | QLD | 06-Apr-2019 | 07-Apr-2019 | 01-Jan-2019 | 06-Feb-2019 | Security |
| [Web Directions Design](https://www.webdirections.org/design/) | VIC | 11-Apr-2019 | 12-Apr-2019 |  |  | Design, UX |
| [Global Azure Bootcamp (Perth)](https://sessionize.com/global-azure-bootcamp-perth-2019/) | WA | 27-Apr-2019 | 27-Apr-2019 | 18-Dec-2018 | 13-Apr-2019 | Microsoft Azure |
| [Global Azure Bootcamp (Melbourne)](https://sessionize.com/global-azure-bootcamp-melbourne) | VIC | 27-Apr-2019 | 27-Apr-2019 | 01-Feb-2019 | 10-Mar-2019 | Microsoft Azure |
| Global Azure Bootcamp (Sydney) [Attend](https://www.meetup.com/en-AU/Azure-Sydney-User-Group/events/256253065/) - [Speak](https://sessionize.com/sydney-azure-global-bootcamp/) | NSW | 27-Apr-2019 | 27-Apr-2019 | 22-Jan-2019 | 15-Mar-2019 | Microsoft Azure |
| [Pivot Summit](https://www.pivotsummit.com.au/) | VIC | 03-May-2019 | 04-May-2019 |  |  | Various |
| [YOW! Data](https://data.yowconference.com.au/) | NSW | 06-May-2019 | 07-May-2019 | 01-Jan-2019 | 22-Mar-2019 | Data |
| [Rails Camp](https://rails.camp/) | WA | 10-May-2019 | 13-May-2019 | | | Ruby |
| [YOW! Lambda Jam](https://lambdajam.yowconference.com.au/) | VIC | 13-May-2019 | 15-May-2019 | 22-Mar-2019 | 22-Mar-2019 | Functional Programming |
| [Voxxed Days](https://australia.voxxeddays.com/) | VIC | 13-May-2019 | 14-May-2019 | 24-Oct-2018 | 11-Feb-2019 | Various |
| [Catalyst Conference](https://catalystaustralia.girlsintech.org/) | VIC | 15-May-2019 | 16-May-2019 | | | Women in Tech |
| [Voxxed Days](https://australia.voxxeddays.com/) | NSW | 16-May-2019 | 17-May-2019 | 25-Oct-2018 | 11-Feb-2019 | Various |
| [Australian Test & Tech Automation Conference (ATTAC)](https://attac.tech/) | VIC | 24-May-2019 | 24-May-2019 | | | Testing, QA |
| [LAST Conference Adelaide](https://www.lastconference.com/adelaide/) | SA | 30-May-2019 | 30-May-2019 | | | Agile, Lean, Systems Thinking |
| [SQL Saturday Brisbane](https://www.sqlsaturday.com/838/eventhome.aspx) | QLD | 31-May-2019 | 01-Jun-2019 | 01-Jan-2019 | 02-Apr-2019 | Data |
| [SQL Saturday Melbourne](https://www.sqlsaturday.com/865/eventhome.aspx) | VIC | 14-Jun-2019 | 15-Jun-2019 | 01-Feb-2019 | 16-Apr-2019 | Data |
| [OzSecCon](https://www.ozseccon.com/) | VIC | 14-Jun-2019 | 16-Jun-2019 |  | 31-Mar-2019 | Security |
| [Global DevOps Bootcamp](https://www.eventbrite.com/e/global-devops-bootcamp-telstrareadify-tickets-57006764768) | NSW | 15-Jun-2019 | 15-Jun-2019 | | | DevOps, Azure |
| [Web Directions Code](https://www.webdirections.org/code/) | VIC | 20-Jun-2019 | 21-Jun-2019 |  |  | JavaScript, Front End, Design |
| [AgileAus 2019](http://agileaustralia.com.au/2019/) | NSW | 25-Jun-2019 | 26-Jun-2019 |  |  | Agile |
| [XConf](https://www.thoughtworks.com/xconf-au-2019) | NSW | 23-Jul-2019 | 23-Jul-2019 |  |  | Thoughtworks |
| [XConf](https://www.thoughtworks.com/xconf-au-2019) | VIC | 25-Jul-2019 | 25-Jul-2019 |  |  | Thoughtworks |
| [Container Camp](https://2019.container.camp/au/) | NSW | 25-Jul-2019 | 26-Jul-2019 |  |  | Container, Kubernetes, Docker |
| [LAST Conference Melbourne](https://www.lastconference.com/melbourne/) | VIC | 30-Jul-2019 | 30-Jul-2019 | | | Agile, Lean, Systems Thinking |
| [Web Directions Product](https://www.webdirections.org/product/) | VIC | 1-Aug-2019 | 2-Aug-2019 |  |  | Product, Design |
| [PyCon AU](https://2019.pycon-au.org/) | NSW | 2-Aug-2019 | 6-Aug-2019 | 3-Apr-2019 | 5-May-2019 | Python |
| [SQL Saturday Sydney](https://www.sqlsaturday.com/875/eventhome.aspx) | NSW | 03-Aug-2019 | 03-Aug-2019 |  |  | Data |
| [DDD Perth](https://dddperth.com/) | WA | 3-Aug-2019 | 3-Aug-2019 | 30-Apr-2019 | 2-Jun-2019 | Various |
| [DDD Melbourne](https://dddmelbourne.com/) | VIC | 10-Aug-2019 | 10-Aug-2019 | 13-Apr-2019 | 15-Jun-2019 | Various |
| [Australia Summit for Microsoft Business Applications](https://www.australiasummit.com/home) | VIC | 21-Aug-2019 | 23-Aug-2019 | | | Data, Business Applications, Power Platform |
| [ServerlessDays Sydney](https://sydney.serverlessdays.io/) | NSW | 27-Aug-2019 | 27-Aug-2019 | 25-Mar-2019 | 20-May-2019 | Serverless |
| [ServerlessDays Melbourne](https://www.serverlessdays.me/) | VIC | 29-Aug-2019 | 29-Aug-2019 | 01-May-2019 | 09-Jun-2019 | Serverless |
| [LAST Conference Sydney](https://www.lastconference.com/sydney/) | NSW | 29-Aug-2019 | 29-Aug-2019 | | | Agile, Lean, Systems Thinking |
| [YOW! Perth](https://yowconference.com/) | WA | 4-Sep-2019 | 5-Sep-2019 |  |  | Various |
| [Global AI Night](https://www.meetup.com/Melbourne-Azure-Nights/events/264397932/) | VIC | 5-Sep-2019 | 5-Sep-2019 | | | ML, A, Bots|
| [ComponentsConf](https://www.componentsconf.com.au/) | VIC | 6-Sep-2019 | 6-Sep-2019 | 01-Dec-2018 | 06-Apr-2019 | Front End |
| [SQL Saturday Perth](https://www.sqlsaturday.com/894/eventhome.aspx) | WA | 07-Sep-2019 | 07-Sep-2019 |  |  | Data |
| [YOW! Business Agility Conference](https://businessagility.yowconference.com.au/) | VIC | 16-Sep-2019 | 17-Sep-2019 | 01-Feb-2019 | 30-Apr-2019 | Agile |
| [APIDays](https://www.apidays.co) | VIC | 00-Sep-2019 | 00-Sep-2019 | 00-Jan-2019 | 00-Aug-2019 | Various |
| [DDD Sydney](https://next.dddsydney.com.au) | NSW | 21-Sep-2019 | 21-Sep-2019 | 10-Jun-2019 | 14-Jul-2019 | Various |
| [LAST Conference Brisbane](https://www.lastconference.com/brisbane/) | QLD | 04-Oct-2019 | 04-Oct-2019 | | | Agile, Lean, Systems Thinking |
| [Devopsdays Sydney 2019](https://devopsdays.org/events/2019-sydney/welcome/) | NSW | 10-Oct-2019 | 11-Oct-2019 | 03-Jun-2019  | 05-Aug-2019 | Various |
| [NDC Sydney 2019](https://ndcsydney.com/) | NSW | 14-Oct-2019 | 18-Oct-2019 | 10-Feb-2019  | 15-Jun-2019 | Various |
| [JetBrains Night Sydney 2019](https://info.jetbrains.com/jetbrains-night-sydney-2019.html) | NSW | 15-Oct-2019 | 15-Oct-2019 | | | Various |
| [LAST Conference ACT](https://www.lastconference.com/canberra/) | ACT | 16-Oct-2019 | 16-Oct-2019 | | | Agile, Lean, Systems Thinking |
| [JetBrains Meetup Melbourne 2019](https://info.jetbrains.com/jetbrains-meetup-melbourne-2019.html) | VIC | 21-Oct-2019 | 21-Oct-2019 | | | Various |
| [JetBrains Night Brisbane 2019](https://info.jetbrains.com/jetbrains-night-brisbane-2019.html) | QLD | 22-Oct-2019 | 22-Oct-2019 | | | Various |
| [GopherCon AU](https://gophercon.com.au/) | NSW | 30-Oct-2019 | 1-Nov-2019 | 01-May-2019 | 30-Jun-2019 | Golang |
| [Web Directions Summit](https://www.webdirections.org/wds/) | NSW | 31-Oct-2019 | 1-Nov-2019 |  |  | Front End, JavaScript, Product, Design |
| [Latency Conf](https://www.latencyconf.io/) | WA | 14-Nov-2019 | 15-Nov-2019 |  |  | Cloud Native |
| [GDG DevFest Melbourne](https://www.gdgmelbourne.com/devfest) | VIC | 09-Nov-2019 | 09-Nov-2019 |  |  | Google Tech |
| [DDD Adelaide](https://dddadelaide.com) | SA | 23-Nov-2019 | 23-Nov-2019 | 1-Aug-2019 | 4-Sep-2019 | Various |
| [TConf](https://tconf.io/) | VIC | 29-Nov-2019 | 29-Nov-2019 | 01-May-2019 | 30-Jun-2019  | Testing, QA |
| [YOW! Sydney](http://sydney.yowconference.com.au/) | NSW | 05-Dec-2019 | 06-Dec-2019 |  |  | Various |
| [Microservices, Containers & Serverless Day](https://1point21gws.com/microservices/melbourne/) | VIC | 05-Dec-2019 | 05-Dec-2019 |  |  | Microservices |
| [YOW! Brisbane](http://brisbane.yowconference.com.au/) | QLD | 09-Dec-2019 | 10-Dec-2019 |  |  | Various |
| [YOW! Melbourne](http://melbourne.yowconference.com.au/) | VIC | 12-Dec-2019 | 13-Dec-2019 |  |  | Various |
| [Kubernetes Forum Sydney](https://events.linuxfoundation.org/events/kubernetes-forum-sydney-2019/) | NSW | 12-Dec-2019 | 13-Dec-2019 |  31-Jul-2019 | 06-Sep-2019 | Kubernetes, CNCF-related projects |

================================================
FILE: Past Events/2020.md
================================================

# Australian Developer Events

## Event Archive

### 2020

| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |
| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |
| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/223) | WA | 18-Jan-2020 | 18-Jan-2020 ||| Tech Talks |
| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/236) | VIC | 18-Jan-2020 | 18-Jan-2020 ||| Tech Talks |
| [Global Diversity CFP Day (Sydney)](https://www.globaldiversitycfpday.com/events/215) | NSW | 18-Jan-2020 | 18-Jan-2020 ||| Tech Talks |
| [Global Diversity CFP Day (Newcastle)](https://www.globaldiversitycfpday.com/events/245) | NSW | 18-Jan-2020 | 18-Jan-2020 ||| Tech Talks |
| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/240) | QLD | 18-Jan-2020 | 18-Jan-2020 ||| Tech Talks |
| [Australian Accessibility Conference](https://ozewai.org/conference/) | WA | 11-Feb-2020 | 13-Feb-2020 ||| Accessibility | 
| [Microsoft Ignite Tour](https://www.microsoft.com/en-au/ignite-the-tour/sydney) | NSW | 13-Feb-2020 | 14-Feb-2020 ||| Cloud Infrastructure |
| [RubyConf](https://www.rubyconf.org.au/2020) | VIC | 20-Feb-2020 | 21-Feb-2020 || 8-Nov-2019 | Ruby |
| [ReactConf AU](https://www.reactconf.com.au) | NSW | 27-Feb-2020 | 28-Feb-2020 || | JavaScript, React |
| [DevOps Talks Conference](https://devopstalks.com/au/devops.html/) | VIC | 18-Mar-2020 | 20-Mar-2020 || 15-Jan-2020 | DevOps |
| [YOW! Data](https://data.yowconference.com.au/) | NSW | 29-Apr-2020 | 1-May-2020 || 16-Feb-2020 | Data |
| [BSides Canberra](https://www.bsidescbr.com.au/) | ACT | 1-May-2020 |2-May-2020 || 15-Jan-2020 | Security |
| [Pivot Summit](https://www.pivotsummit.com.au/) | VIC | 3-May-2020 | 4-May-2020 ||| Various |
| [YOW! Lambda Jam](https://lambdajam.yowconference.com.au/) | VIC | 6-May-2020 | 8-May-2020 || 23-Feb-2020 | Functional Programming |VIC | 13-May-2020 | 14-May-2020 ||| Women in Tech |
| [Web Directions Code Leaders](https://www.webdirections.org/leaders/) | VIC | 03-Jun-2020 | 03-Jun-2020 ||| Leadership |
| [Web Directions Code](https://www.webdirections.org/code/) | VIC | 04-Jun-2020 | 05-Jun-2020 ||| Web |
| [AgileAus 2020](http://agileaustralia.com.au/2020/) | NSW | 15-Jun-2020 | 16-Jun-2020 ||| Agile |
| [Web Directions Product](https://www.webdirections.org/product/) | VIC | 01-Jul-2020 | 02-Jul-2020 ||| Product Design |
| [Web Directions Design Leaders](https://www.webdirections.org/designleaders/) | VIC | 03-Jul-2020 | 03-Jul-2020 ||| Leadership |
| [NDC Melbourne 2020](https://ndcmelbourne.com/) | online | 27-Jul-2020 | 30-Jul-2020 | [01-Jan-2020](https://sessionize.com/ndc-melbourne-2020/) | [05-Apr-2020](https://sessionize.com/ndc-melbourne-2020/) | Various |
| [DataEngBytes 2020](https://dataengconf.com.au/) | online | 20-Aug-2020 | 21-Aug-2020 | [01-Jun-2020](https://sessionize.com/dataengbytes/) | [01-Jul-2020](https://sessionize.com/dataengbytes/) | Data Engineering, Machine Learning |
| [ANZ ServerlessDays 2020](https://anz.serverlessdays.io/) | online | 04-Sep-2020 | 04-Sep-2020 | [10-Jun-2020](https://sessionize.com/serverlessdays-anz-2020) | [02-Aug-2020](https://sessionize.com/serverlessdays-anz-2020) | Serverless |
| [PyConline AU 2020](https://2020.pycon.org.au) | online | 04-Sep-2020 | 06-Sep-2020 | [18-Jun-2020](https://2020.pycon.org.au/speak/) | [12-Jul-2020](https://2020.pycon.org.au/speak/) | Python |
| [API Days Melbourne](https://www.apidays.co/melbourne) | online | 15-Sep-2020 | 16-Sep-2020 | [01-Feb-2020](https://apidays.typeform.com/to/J1snsg) | NA | Connection, Automation, Intelligence |
| [NDC Sydney 2020](https://ndcsydney.com/) | NSW | 12-Oct-2020 | 16-Oct-2020 | [10-Feb-2020](https://sessionize.com/ndc-sydney-2020/) | [07-Jun-2020](https://sessionize.com/ndc-sydney-2020/) | Various |
| [Latency Conf 2020](https://www.latencyconf.io/home) | WA | 18-Nov-2020 | 19-Nov-2020 | Open | [16-Aug-2020](https://docs.google.com/forms/d/e/1FAIpQLSdjvhe8LgAycAsoES_PiNqhZzVhxVSN3pwIhoat3vFcfY8Nrw/viewform) | Cloud |


================================================
FILE: Past Events/2022.md
================================================

# Australian Developer Events

## Event Archive

### 2022

| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |
| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |
| [YOW! Lambda Jam 2022](https://skillsmatter.com/conferences/13660-yow-lambdajam-2022) | Virtual | 17-May-2022 | 18-May-2022 | [info](https://skillsmatter.com/conferences/13660-yow-lambdajam-2022#get_involved) | | functional programming|
| [Rust Forum](https://skillsmatter.com/conferences/13771-rust-forum) | Virtual | 24-May-2022 | 24-May-2022 |  | | rust|
| [YOW! Data 2022](https://skillsmatter.com/conferences/13659-yow-data-2022) | Virtual | 1-Jun-2022 | 2-Jun-2022 | [info](https://skillsmatter.com/conferences/13659-yow-data-2022#get_involved) | | Big Data, analytics, machine learning|
| [FullStack eXchange](https://skillsmatter.com/conferences/13727-fullstack-exchange-online) | Virtual | 27-Jul-2022 | 28-Jul-2022 | [now](https://skillsmatter.com/conferences/13727-fullstack-exchange-online#get_involved) | 28-Apr-2022| various|
| [Bazel eXchange](https://skillsmatter.com/conferences/13682-bazel-exchange) | Virtual | 21-Jun-2022 | 22-Jun-2022 | [now](https://skillsmatter.com/conferences/13682-bazel-exchange#get_involved) | 23-Mar-2022| Bazel|
| [DDD Perth 2022](https://dddperth.com/) | Perth | 10-Sep-2022 | 10-Sept-2022 |  | | various |
| [API Days](https://www.apidays.global/australia/) | Melbourne | 14-Sep-2022 | 15-Sept-2022 |  | | API, Integration, Networks, Business|
| [YOW! Perth 2022](https://skillsmatter.com/conferences/13732-yow-perth-2022) | Perth | 19-Sep-2022 | 20-Sept-2022 |  | | various|
| [YOW! London 2022](https://skillsmatter.com/conferences/13691-yow-london-online) | Virtual | 22-Sep-2022 | 23-Sept-2022 |  | | various|
| [NDC Sydney 2022](https://ndcsydney.com/) | NSW | 10-Oct-2022 | 14-Oct-2022||| Tech Talks, Workshops |
| [Testing Talks Conference](https://www.testingtalks.com.au/) | VIC | 20-Oct-2022 | 20-Oct-2022 | Testing || Tech Talks |
| [Product Elevation 2022](https://skillsmatter.com/conferences/13681-product-elevation-2022) | Virtual | 9-Nov-2022 | 10-Nov-2022 | [now](https://skillsmatter.com/conferences/13681-product-elevation-2022#get_involved) | 31-May-2022 | product, UX|
| [Web Directions Code Leaders](https://webdirections.org/leaders) | NSW | 30-Nov-2022 | 30-Nov-2022 | Engineering Leadership|| Tech Talks |
| [Web Directions Summit](https://webdirections.org/summit) | NSW | 01-Dec-2022 | 02-Dec-2022 | Product, design, FE dev|| Tech Talks |
| [DDD Brisbane 2022](https://www.dddbrisbane.com/) | QLD | 03-Dec-2022 | 03-Dec-2022 | 05-Sept-2022 | 03-Oct-2022 | Tech Talks |
| [YOW! Brisbane 2022](https://www.skillsmatter.com/conferences/13735-yow-brisbane-2022) | Brisbane | 5-Dec-2022 | 6-Dec-2022 |  |  | various|
| [YOW! Melbourne 2022](https://www.skillsmatter.com/conferences/13733-yow-melbourne-2022) | Melbourne | 8-Dec-2022 | 9-Dec-2022 |  |  | various|
| [Haskell eXchange 2022](https://skillsmatter.com/conferences/13688-haskell-exchange-2022) | Virtual | 8-Dec-2022 | 9-Dec-2022 |  |  | Haskell|
| [YOW! Sydney 2022](https://www.skillsmatter.com/conferences/13734-yow-sydney-2022) | Sydney | 12-Dec-2022 | 13-Dec-2022 |  |  | various|


================================================
FILE: Past Events/2024.md
================================================

# Australian Developer Events

## Event Archive

### 2024

| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |
| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |
| [YOW! Tech Leaders Summit](https://yowcon.com/melbourne-2024) | Melbourne | 11-Sep-2024 | | | | Tech Leadership |
| [YOW! Tech Leaders Summit](https://yowcon.com/brisbane-2024) | Brisbane | 12-Sep-2024 | | | | Tech Leadership |
| [YOW! Tech Leaders Summit](https://yowcon.com/sydney-2024) | Sydney | 13-Sep-2024 | | | | Tech Leadership |
| [DataEngBytes](https://dataengconf.com.au/) | Sydney | 24-Sep-2024 | | 18-Mar-2024 | 14-Jul-2024 | Data |
| [DataEngBytes](https://dataengconf.com.au/) | Perth | 27-Sep-2024 | | 18-Mar-2024 | 14-Jul-2024 | Data |
| [LAST Conference](https://www.lastconference.com/brisbane/) | Brisbane | 27-Sep-2024 | | | | Lean Agile Systems Thinking |
| [DataEngBytes](https://dataengconf.com.au/) | Melbourne | 1-Oct-2024 | | 18-Mar-2024 | 14-Jul-2024 | Data |
| [DataEngBytes](https://dataengconf.com.au/) | Auckland | 4-Oct-2024 | | 18-Mar-2024 | 14-Jul-2024 | Data |
| [GDG Melbourne DevFest](https://gdgmelbourne.com/) | Melbourne | 5-Oct-2024 | | 30-Jul-2024 | 30-Aug-2024 | Google Tech |
| [APIDays](https://www.apidays.global/australia/) | Melbourne | 16-Oct-2024 | 17-Oct-2024 | NOW | 16-Sep-2024 | APIs |
| [Testing Talks](https://www.testingtalks.com.au/upcoming-events/testing-talks-conference-2024-melbourne) | Melbourne | 17-Oct-2024 | | Now? | ? | Testing |
| [LAST Conference](https://www.lastconference.com/sydney/) | Sydney | 18-Oct-2024 | | | | Lean Agile Systems Thinking |
| [GopherCon](https://gophercon.com.au/) | Sydney | 7-Nov-2024 | 8-Nov-2024 | NOW | 30-Aug-2024 | Golang |
| [DDD Perth](https://dddperth.com/) | Perth | 16-Nov-2024 | | | 12-Jul-2023 | Various |
| [DDD Adelaide](https://dddadelaide.com/) | Adelaide | 23-Nov-2024 | | 1-Aug-2024 | 6-Sep-2024 | Various |
| [Web Directions Developer Summit](https://webdirections.org/dev-summit/) | Sydney | 27-Nov-2024 | 28-Nov-2024 | | | Web Dev |
| [LAST Conference](https://clubhouse.lastconference.com/lastmel24/) | Melbourne | 29-Nov-2024 | | | | Lean Agile Systems Thinking |
| [YOW!](https://yowcon.com/melbourne-2024) | Melbourne | 5-Dec-2024 | 6-Dec-2024 | | | Various |
| [YOW!](https://yowcon.com/brisbane-2024) | Brisbane | 9-Dec-2024 | 10-Dec-2024 | | | Various |
| [YOW!](https://yowcon.com/sydney-2024) | Sydney | 12-Dec-2024 | 13-Dec-2024 | | | Various |


================================================
FILE: Past Events/2025.md
================================================
# Australian Developer Events

## Event Archive

### 2025


| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |
| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |
| [NDC Sydney](https://tickets.ndcconferences.com/tickets/index/sydney2024) | Sydney | 12-Feb-2025 | 16-Feb-2025 | | | Various |
| [DDD Melbourne](https://www.dddmelbourne.com/) | Melbourne | 22-Feb-2025 | | | | Various |
| [NDC Melbourne](https://tickets.ndcconferences.com/tickets/index/melbourne2025) | Melbourne | 28-Apr-2025 | 1-May-2025 | | | Various |
| [Vogue Codes Summit](https://www.vogue.com.au/vogue-codes) | Sydney | 14-Jun-2025 | | | | Women in Tech |
| [YOW! Tech Leaders Summit Melbourne](https://yowcon.com/tech-leaders-melbourne-2025) | Melbourne | 19-Jun-2025 | | | | Tech Leadership |
| [YOW! Tech Leaders Summit Sydney](https://yowcon.com/tech-leaders-sydney-2025) | Sydney | 18-Jun-2025 | | | | Tech Leadership |
| [YOW! Tech Leaders Summit Brisbane](https://yowcon.com/tech-leaders-brisbane-2025) | Brisbane | 17-Jun-2025 | | | | Tech Leadership |
| [DataEngBytes Melbourne](https://dataengbytes.com/melbourne) | Melbourne | 24-Jul-2025 | 25-Jul-2025 | | | Data Eng & MLOps |
| [DataEngBytes Sydney](https://dataengbytes.com/sydney) | Sydney | 29-Jul-2025 | 30-Jul-2025 | | | Data Eng & MLOps |
| [PyConAU](https://2025.pycon.org.au/) | Melbourne | 12-Sep-2025 | 16-Sep-2025 | | | Python |
| [Testing Talks](https://www.testingtalks.com.au/) | Melbourne | 9-Oct-2025 | | | | Testing |
| [apidays](https://www.apidays.global/events/australia) | Melbourne | 29-Oct-2025 | 30-Oct-2025 | | | APIs |
| [Web Directions Developer Summit](https://webdirections.org/dev-summit/index.php) | Sydney | 19-Nov-2025 | 20-Nov-2025 | | | Web |
| [LAST Conference](https://clubhouse.lastconference.com/lastmel25/) | Melbourne | 21-Nov-2025 | | | | Various |
| [DevOpsDays](https://devopsdays.org/events/2025-wollongong/welcome/) | Wollongong | 27-Nov-2025 | 28-Nov-2025 | | | Various |
| [YOW! Melbourne](https://yowcon.com/melbourne-2025) | Melbourne | 4-Dec-2025 | 5-Dec-2025 | | | Various |
| [YOW! Brisbane](https://yowcon.com/brisbane-2025) | Brisbane | 8-Dec-2025 | 9-Dec-2025 | | | Various |
| [YOW! Sydney](https://yowcon.com/sydney-2025) | Sydney | 11-Dec-2025 | 12-Dec-2025 | | | Various |




================================================
FILE: Past Events/OTHER.md
================================================

# Australian Developer Events

## Other Developer Conference Lists

* [2016 Global Web Development Conferences](https://github.com/ryanburgess/2016-conferences)
* [2017 Global Web Development Conferences](https://github.com/ryanburgess/2017-conferences)
* [2018 Global Web Development Conferences](https://github.com/ryanburgess/2018-conferences)
* [2019 Global Web Development Conferences](https://github.com/ryanburgess/2019-conferences)
* [2020 Global Web Development Conferences](https://github.com/ryanburgess/2019-conferences)


================================================
FILE: README.md
================================================
# Australian Developer Events

We've collated a list of all the events in Australia that might be of interest to software developers. Technical content, leadership, software design, agility, and more.

## Upcoming Events

| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |
| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |
| [DDD Melbourne 2026](https://www.dddmelbourne.com/) | Melbourne | 21-Feb-2026 | | | 30-Sep-2025 | Various |
| [Programmable Melbourne 2026](https://www.programmable.tech/) | Melbourne | 17-Mar-2026 | | | 2-Dec-2025 | Various |
| [Programmable Sydney 2026](https://www.programmable.tech/) | Sydney | 19-Mar-2026 | | | 2-Dec-2025 | Various |
| [NDC Sydney 2026](https://ndcsydney.com/) | Sydney | 22-Apr-2026 | 24-Apr-2026 | | 6-Dec-2025 | Various |
| [AI Engineer Melbourne](https://webdirections.org/ai-engineer/index.php) | Melbourne | 3-Jun-2026 | 4-Jun-2026 | | 28-Feb-2026 | Various |

## Prior Years

- [2025 Events](Past%20Events/2025.md)
- [2024 Events](Past%20Events/2024.md)
- [2022 Events](Past%20Events/2022.md)
- [2020 Events](Past%20Events/2020.md)
- [2019 Events](Past%20Events/2019.md)
- [2018 Events](Past%20Events/2018.md)
- [2017 Events](Past%20Events/2017.md)
- [2016 Events](Past%20Events/2016.md)

## Meetups

- [ACT](Meetups/ACT.md)
- [NSW](Meetups/NSW.md)
- [NT](Meetups/NT.md)
- [QLD](Meetups/QLD.md)
- [SA](Meetups/SA.md)
- [TAS](Meetups/TAS.md)
- [VIC](Meetups/VIC.md)
- [WA](Meetups/WA.md)

## More Information

### FAQ

Got a question? [The FAQ](FAQ.md) probably has the answer you need.

### Other Conferences

There are lists of conferences that others are compiling too. You can [find them here](Past%20Events/OTHER.md).

### Can I contribute, or add an event?

Of course! We'd love you to! See the [contribution guide](CONTRIBUTING.md) for how.


================================================
FILE: website/.gitignore
================================================
# dependencies
node_modules/

# build output
dist/

# astro generated files
.astro/

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*

# env files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# OS/editor files
.DS_Store
Thumbs.db
.vscode/*
!.vscode/extensions.json

================================================
FILE: website/astro.config.mjs
================================================
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';

export default defineConfig({
  site: 'https://devevents.io',
  integrations: [
    preact({ compat: false }),
  ],
  output: 'static',
});


================================================
FILE: website/package.json
================================================
{
  "name": "devevents-website",
  "type": "module",
  "version": "1.0.0",
  "scripts": {
    "dev": "astro dev",
    "build": "astro build",
    "preview": "astro preview"
  },
  "dependencies": {
    "@astrojs/preact": "^5.0.2",
    "@astrojs/sitemap": "^3.7.1",
    "astro": "^6.0.8",
    "mdast-util-to-string": "^4.0.0",
    "preact": "^10.24.0",
    "remark-gfm": "^4.0.0",
    "remark-parse": "^11.0.0",
    "unified": "^11.0.5"
  },
  "devDependencies": {
    "@types/node": "^25.0.0",
    "typescript": "^5.5.0"
  }
}


================================================
FILE: website/public/robots.txt
================================================
User-agent: *
Allow: /

Sitemap: https://devevents.io/sitemap-index.xml


================================================
FILE: website/src/components/EventCard.astro
================================================
---
import type { Event } from '@data/types';
import { formatDate } from '@data/parse-date';

interface Props {
  event: Event;
}

const { event } = Astro.props;

const now = new Date();
const cfpOpen = event.cfpOpen && event.cfpClose
  ? now >= event.cfpOpen && now <= event.cfpClose
  : null;
const cfpClosed = event.cfpClose && now > event.cfpClose;

const dateStr = event.dateFrom
  ? event.dateTo && event.dateFrom.toDateString() !== event.dateTo.toDateString()
    ? `${formatDate(event.dateFrom)} – ${formatDate(event.dateTo!)}`
    : formatDate(event.dateFrom)
  : event.dateFromRaw || 'TBC';
---

<article class="event-card">
  <div class="event-card__header">
    <span class="state-pill" data-state={event.state}>{event.state === 'VIRTUAL' ? 'Virtual' : event.state}</span>
    {event.tags.slice(0, 3).map((tag) => (
      <span class="tag-chip">{tag}</span>
    ))}
  </div>
  <div class="event-card__name">
    {event.url
      ? <a href={event.url} target="_blank" rel="noopener noreferrer">{event.name}</a>
      : event.name
    }
  </div>
  <div class="event-card__date">{dateStr}</div>
  {(event.cfpOpenRaw || event.cfpCloseRaw) && (
    <div class="event-card__cfp">
      {cfpOpen === true && (
        <span class="cfp-open">CFP open</span>
      )}
      {cfpOpen === false && cfpClosed && (
        <span class="cfp-closed">CFP closed</span>
      )}
      {cfpOpen === null && (event.cfpOpenRaw || event.cfpCloseRaw) && (
        <span>CFP: {[event.cfpOpenRaw, event.cfpCloseRaw].filter(Boolean).join(' – ')}</span>
      )}
      {cfpOpen === false && !cfpClosed && event.cfpCloseRaw && (
        <span>CFP closes {event.cfpCloseRaw}</span>
      )}
    </div>
  )}
</article>


================================================
FILE: website/src/components/EventTable.astro
================================================
---
import type { Event } from '@data/types';
import { formatDate } from '@data/parse-date';

interface Props {
  events: Event[];
}

const { events } = Astro.props;
---

{events.length === 0 ? (
  <p class="empty-state">No events found.</p>
) : (
  <div class="data-table__wrap">
    <table class="data-table">
      <thead>
        <tr>
          <th>Event</th>
          <th>State</th>
          <th>Date</th>
          <th>Tags</th>
        </tr>
      </thead>
      <tbody>
        {events.map((e) => {
          const dateStr = e.dateFrom
            ? e.dateTo && e.dateFrom.toDateString() !== e.dateTo.toDateString()
              ? `${formatDate(e.dateFrom)} – ${formatDate(e.dateTo!)}`
              : formatDate(e.dateFrom)
            : e.dateFromRaw || 'TBC';

          return (
            <tr>
              <td>
                {e.url
                  ? <a href={e.url} target="_blank" rel="noopener noreferrer">{e.name}</a>
                  : e.name
                }
              </td>
              <td><span class="state-pill" data-state={e.state}>{e.state === 'VIRTUAL' ? 'Virtual' : e.state}</span></td>
              <td style="white-space: nowrap">{dateStr}</td>
              <td>
                <div style="display:flex;flex-wrap:wrap;gap:0.3rem">
                  {e.tags.map((tag) => <span class="tag-chip">{tag}</span>)}
                </div>
              </td>
            </tr>
          );
        })}
      </tbody>
    </table>
  </div>
)}


================================================
FILE: website/src/components/FilterIsland.tsx
================================================
import { useState } from 'preact/hooks';
import type { Event, StateCode } from '@data/types';

interface Props {
  events: Event[];
}

const STATE_LABELS: Record<string, string> = {
  ACT: 'ACT', NSW: 'NSW', NT: 'NT', QLD: 'QLD',
  SA: 'SA', TAS: 'TAS', VIC: 'VIC', WA: 'WA',
  VIRTUAL: 'Virtual', ALL: 'All', OTH: 'Other',
};

// Astro serializes Date objects as ISO strings — coerce back to Date
function toDate(val: unknown): Date | null {
  if (!val) return null;
  if (val instanceof Date) return isNaN(val.getTime()) ? null : val;
  const d = new Date(val as string);
  return isNaN(d.getTime()) ? null : d;
}

function fmt(val: unknown): string {
  const d = toDate(val);
  if (!d) return '';
  return d.toLocaleDateString('en-AU', { day: 'numeric', month: 'short', year: 'numeric' });
}

function fuzzyMatch(query: string, target: string): boolean {
  const q = query.toLowerCase();
  const t = target.toLowerCase();
  let qi = 0;
  for (let ti = 0; ti < t.length && qi < q.length; ti++) {
    if (t[ti] === q[qi]) qi++;
  }
  return qi === q.length;
}

export default function FilterIsland({ events }: Props) {
  const [search, setSearch] = useState('');
  const [selectedStates, setSelectedStates] = useState<StateCode[]>([]);
  const [selectedTags, setSelectedTags] = useState<string[]>([]);
  const [cfpOnly, setCfpOnly] = useState(false);

  const now = new Date();

  const allStates = [...new Set(events.map((e) => e.state))].sort() as StateCode[];
  const allTags = [...new Set(events.flatMap((e) => e.tags))].sort();
  const hasCfpData = events.some((e) => { const d = toDate(e.cfpClose); return d && d >= now; });

  const filtered = events.filter((e) => {
    if (search.trim()) {
      const q = search.trim();
      const searchable = [e.name, STATE_LABELS[e.state] ?? e.state, ...e.tags].join(' ');
      if (!fuzzyMatch(q, searchable)) return false;
    }
    if (selectedStates.length > 0 && !selectedStates.includes(e.state)) return false;
    if (selectedTags.length > 0 && !e.tags.some((t) => selectedTags.includes(t))) return false;
    if (cfpOnly) {
      const cfpClose = toDate(e.cfpClose);
      if (!cfpClose || now > cfpClose) return false;
    }
    return true;
  });

  function toggleState(s: StateCode) {
    setSelectedStates((prev) =>
      prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]
    );
  }

  function toggleTag(t: string) {
    setSelectedTags((prev) =>
      prev.includes(t) ? prev.filter((x) => x !== t) : [...prev, t]
    );
  }

  function clearAll() {
    setSearch('');
    setSelectedStates([]);
    setSelectedTags([]);
    setCfpOnly(false);
  }

  const hasFilters = search.trim() !== '' || selectedStates.length > 0 || selectedTags.length > 0 || cfpOnly;

  return (
    <div>
      {/* Search */}
      <div style={{ marginBottom: '1rem' }}>
        <input
          type="search"
          placeholder="Search events…"
          value={search}
          onInput={(e) => setSearch((e.target as HTMLInputElement).value)}
          style={{
            width: '100%',
            padding: '0.5rem 0.75rem',
            fontSize: '0.95rem',
            border: '1.5px solid var(--color-border)',
            borderRadius: 'var(--radius)',
            background: 'var(--color-bg)',
            color: 'var(--color-text)',
            fontFamily: 'inherit',
            boxSizing: 'border-box',
          }}
        />
      </div>

      {/* Filters */}
      <div style={{ marginBottom: '1.5rem' }}>
        {allStates.length > 0 && (
          <div style={{ marginBottom: '0.75rem' }}>
            <div style={{ fontSize: '0.78rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--color-text-muted)', marginBottom: '0.4rem' }}>
              State
            </div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.4rem' }} role="group" aria-label="Filter by state">
              {allStates.map((s) => {
                const active = selectedStates.includes(s);
                return (
                  <button
                    key={s}
                    onClick={() => toggleState(s)}
                    aria-pressed={active}
                    style={{
                      padding: '4px 12px',
                      borderRadius: '9999px',
                      border: `2px solid ${active ? 'var(--color-accent)' : 'var(--color-border)'}`,
                      background: active ? 'var(--color-accent)' : 'var(--color-surface)',
                      color: active ? '#fff' : 'var(--color-text)',
                      fontWeight: 600,
                      fontSize: '0.85rem',
                      cursor: 'pointer',
                      fontFamily: 'inherit',
                      minHeight: '32px',
                    }}
                  >
                    {active ? '✓ ' : ''}{STATE_LABELS[s] ?? s}
                  </button>
                );
              })}
            </div>
          </div>
        )}

        {allTags.length > 0 && (
          <div style={{ marginBottom: '0.75rem' }}>
            <div style={{ fontSize: '0.78rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--color-text-muted)', marginBottom: '0.4rem' }}>
              Topic
            </div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.4rem' }} role="group" aria-label="Filter by topic">
              {allTags.map((t) => {
                const active = selectedTags.includes(t);
                return (
                  <button
                    key={t}
                    onClick={() => toggleTag(t)}
                    aria-pressed={active}
                    style={{
                      padding: '3px 10px',
                      borderRadius: '9999px',
                      border: `2px solid ${active ? 'var(--color-accent)' : 'var(--color-border)'}`,
                      background: active ? 'var(--color-accent)' : '#f9fafb',
                      color: active ? '#fff' : 'var(--color-text-muted)',
                      fontWeight: 600,
                      fontSize: '0.8rem',
                      cursor: 'pointer',
                      fontFamily: 'inherit',
                      minHeight: '28px',
                    }}
                  >
                    {active ? '✓ ' : ''}{t}
                  </button>
                );
              })}
            </div>
          </div>
        )}

        <div style={{ display: 'flex', alignItems: 'center', gap: '1rem', flexWrap: 'wrap' }}>
          {hasCfpData && (
            <label style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', cursor: 'pointer', fontSize: '0.9rem' }}>
              <input
                type="checkbox"
                checked={cfpOnly}
                onChange={(e) => setCfpOnly((e.target as HTMLInputElement).checked)}
              />
              CFP open now
            </label>
          )}
          {hasFilters && (
            <button
              onClick={clearAll}
              style={{
                background: 'none', border: 'none', cursor: 'pointer',
                color: 'var(--color-text-muted)', fontSize: '0.85rem',
                textDecoration: 'underline', padding: 0, fontFamily: 'inherit',
              }}
            >
              Clear filters
            </button>
          )}
        </div>
      </div>

      {/* Results count */}
      <div style={{ fontSize: '0.85rem', color: 'var(--color-text-muted)', marginBottom: '1rem' }}>
        {filtered.length} event{filtered.length !== 1 ? 's' : ''}
        {hasFilters ? ' matching filters' : ''}
      </div>

      {/* Event cards */}
      {filtered.length === 0 ? (
        <div class="empty-state">
          <p>{hasFilters ? 'No events match your filters.' : 'No upcoming events right now.'}</p>
          {hasFilters && (
            <button
              onClick={clearAll}
              style={{
                background: 'var(--color-accent)', color: '#fff',
                border: 'none', borderRadius: '8px',
                padding: '0.5rem 1.25rem', cursor: 'pointer',
                fontSize: '0.9rem', fontFamily: 'inherit',
                marginTop: '0.5rem',
              }}
            >
              Clear filters
            </button>
          )}
        </div>
      ) : (
        <div class="card-grid">
          {filtered.map((e) => {
            const cfpClose = toDate(e.cfpClose);
            const dateFrom = toDate(e.dateFrom);
            const dateTo = toDate(e.dateTo);

            const cfpIsOpen = cfpClose ? now <= cfpClose : null;

            const dateStr = dateFrom
              ? dateTo && dateFrom.toDateString() !== dateTo.toDateString()
                ? `${fmt(dateFrom)} – ${fmt(dateTo)}`
                : fmt(dateFrom)
              : (e.dateFromRaw as string) || 'TBC';

            return (
              <article key={`${e.name}-${e.dateFromRaw}`} class="event-card">
                <div class="event-card__header">
                  <span class="state-pill" data-state={e.state}>
                    {e.state === 'VIRTUAL' ? 'Virtual' : e.state}
                  </span>
                  {(e.tags as string[]).slice(0, 3).map((tag) => (
                    <span key={tag} class="tag-chip">{tag}</span>
                  ))}
                </div>
                <div class="event-card__name">
                  {e.url
                    ? <a href={e.url as string} target="_blank" rel="noopener noreferrer">{e.name as string}</a>
                    : e.name as string
                  }
                </div>
                <div class="event-card__date">{dateStr}</div>
                {(e.cfpOpenRaw || e.cfpCloseRaw) && (
                  <div class="event-card__cfp">
                    {cfpIsOpen === true && <span class="cfp-open">CFP open</span>}
                    {cfpIsOpen === false && <span class="cfp-closed">CFP closed</span>}
                    {cfpIsOpen === null && (e.cfpOpenRaw || e.cfpCloseRaw) && (
                      <span>CFP: {[e.cfpOpenRaw, e.cfpCloseRaw].filter(Boolean).join(' – ')}</span>
                    )}
                  </div>
                )}
              </article>
            );
          })}
        </div>
      )}
    </div>
  );
}


================================================
FILE: website/src/components/Footer.astro
================================================
---
const year = new Date().getFullYear();
---

<footer class="footer">
  <div class="container footer__inner">
    <span>
      &copy; {year} DevEvents &mdash; Licensed under
      <a href="https://creativecommons.org/publicdomain/zero/1.0/" target="_blank" rel="noopener noreferrer">CC0</a>
    </span>
    <span>
      <a href="https://github.com/vatsalyagoel/DevEvents" target="_blank" rel="noopener noreferrer">GitHub</a>
      &middot;
      <a href="/about">About</a>
      &middot;
      <a href="https://github.com/vatsalyagoel/DevEvents/issues/new" target="_blank" rel="noopener noreferrer">Report an issue</a>
    </span>
  </div>
</footer>


================================================
FILE: website/src/components/Nav.astro
================================================
---
interface Props {
  currentPath: string;
}

const { currentPath } = Astro.props;

const links = [
  { href: '/events', label: 'Events' },
  { href: '/meetups', label: 'Meetups' },
  { href: '/about', label: 'About' },
];
---

<nav class="nav">
  <div class="container nav__inner">
    <a href="/" class="nav__logo">DevEvents</a>
    <ul class="nav__links">
      {links.map(({ href, label }) => (
        <li>
          <a
            href={href}
            class={currentPath.startsWith(href) ? 'active' : ''}
          >
            {label}
          </a>
        </li>
      ))}
    </ul>
    <a
      href="https://github.com/vatsalyagoel/DevEvents/edit/main/README.md"
      class="nav__cta"
      target="_blank"
      rel="noopener noreferrer"
    >
      Add Event
    </a>
    <button class="theme-toggle" aria-label="Toggle dark mode" onclick="toggleTheme()"></button>
  </div>
</nav>

<script is:inline>
  function toggleTheme() {
    var next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';
    document.documentElement.dataset.theme = next;
    localStorage.setItem('theme', next);
  }
</script>


================================================
FILE: website/src/components/UpcomingEventsIsland.tsx
================================================
import type { Event } from '@data/types';

interface Props {
  events: Event[];
}

function toDate(val: unknown): Date | null {
  if (!val) return null;
  if (val instanceof Date) return isNaN(val.getTime()) ? null : val;
  const d = new Date(val as string);
  return isNaN(d.getTime()) ? null : d;
}

function fmt(val: unknown): string {
  const d = toDate(val);
  if (!d) return '';
  return d.toLocaleDateString('en-AU', { day: 'numeric', month: 'short', year: 'numeric' });
}

export default function UpcomingEventsIsland({ events }: Props) {
  const now = new Date();

  if (events.length === 0) {
    return (
      <div class="empty-state">
        <p>No upcoming events right now.</p>
        <p>
          <a href="/events/past">Browse past events &rarr;</a>
        </p>
      </div>
    );
  }

  return (
    <div class="card-grid">
      {events.map((e) => {
        const dateFrom = toDate(e.dateFrom);
        const dateTo = toDate(e.dateTo);
        const cfpClose = toDate(e.cfpClose);
        const cfpIsOpen = cfpClose ? now <= cfpClose : null;

        const dateStr = dateFrom
          ? dateTo && dateFrom.toDateString() !== dateTo.toDateString()
            ? `${fmt(dateFrom)} – ${fmt(dateTo)}`
            : fmt(dateFrom)
          : (e.dateFromRaw as string) || 'TBC';

        return (
          <article key={`${e.name}-${e.dateFromRaw}`} class="event-card">
            <div class="event-card__header">
              <span class="state-pill" data-state={e.state}>
                {e.state === 'VIRTUAL' ? 'Virtual' : e.state}
              </span>
              {e.tags.slice(0, 3).map((tag) => (
                <span key={tag} class="tag-chip">{tag}</span>
              ))}
            </div>
            <div class="event-card__name">
              {e.url
                ? <a href={e.url} target="_blank" rel="noopener noreferrer">{e.name}</a>
                : e.name
              }
            </div>
            <div class="event-card__date">{dateStr}</div>
            {(e.cfpOpenRaw || e.cfpCloseRaw) && (
              <div class="event-card__cfp">
                {cfpIsOpen === true && <span class="cfp-open">CFP open</span>}
                {cfpIsOpen === false && <span class="cfp-closed">CFP closed</span>}
                {cfpIsOpen === null && (
                  <span>CFP: {[e.cfpOpenRaw, e.cfpCloseRaw].filter(Boolean).join(' – ')}</span>
                )}
              </div>
            )}
          </article>
        );
      })}
    </div>
  );
}


================================================
FILE: website/src/data/normalize-state.ts
================================================
import type { StateCode } from './types.js';

const STATE_MAP: Record<string, StateCode> = {
  // Codes
  act: 'ACT', nsw: 'NSW', nt: 'NT', qld: 'QLD',
  sa: 'SA', tas: 'TAS', vic: 'VIC', wa: 'WA',
  all: 'ALL', oth: 'OTH', other: 'OTH',
  virtual: 'VIRTUAL', online: 'VIRTUAL',

  // City names
  canberra: 'ACT',
  sydney: 'NSW', wollongong: 'NSW', newcastle: 'NSW',
  darwin: 'NT',
  brisbane: 'QLD', 'gold coast': 'QLD',
  adelaide: 'SA',
  hobart: 'TAS',
  melbourne: 'VIC', geelong: 'VIC',
  perth: 'WA',

  // International / unknown
  auckland: 'OTH', 'new zealand': 'OTH', global: 'OTH',
};

export function normalizeState(raw: string): StateCode {
  const key = raw.trim().toLowerCase();
  return STATE_MAP[key] ?? 'OTH';
}


================================================
FILE: website/src/data/parse-date.ts
================================================
const MONTH_MAP: Record<string, number> = {
  jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5,
  jul: 6, aug: 7, sep: 8, oct: 9, nov: 10, dec: 11,
};

export function parseEventDate(raw: string): Date | null {
  if (!raw) return null;
  const trimmed = raw.trim();
  if (!trimmed || trimmed.toLowerCase() === 'tbc') return null;

  // Normalise "Sept" → "Sep", "June" → "Jun", etc.
  const normalised = trimmed.replace(/\b(sept)\b/i, 'Sep');

  // Match d-Mmm-yyyy or dd-Mmm-yyyy
  const match = normalised.match(/^(\d{1,2})-([A-Za-z]{3})-(\d{4})$/);
  if (!match) return null;

  const day = parseInt(match[1], 10);
  const monthKey = match[2].toLowerCase();
  const year = parseInt(match[3], 10);

  const month = MONTH_MAP[monthKey];
  if (month === undefined) return null;
  if (day === 0 || day > 31) return null;

  const date = new Date(year, month, day);
  // Validate the date didn't roll over (e.g. Feb 30)
  if (date.getMonth() !== month) return null;

  return date;
}

export function formatDate(date: Date): string {
  return date.toLocaleDateString('en-AU', {
    day: 'numeric',
    month: 'short',
    year: 'numeric',
  });
}


================================================
FILE: website/src/data/parse-events.ts
================================================
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';
import { toString as mdastToString } from 'mdast-util-to-string';
import type { Root, Table, TableRow } from 'mdast';
import type { Event } from './types.js';
import { parseEventDate } from './parse-date.js';
import { normalizeState } from './normalize-state.js';

import { resolve } from 'path';
const DATA_ROOT = resolve(process.cwd(), '..');

function extractLink(cell: TableRow['children'][number]): { text: string; url: string | null } {
  const text = mdastToString(cell).trim();
  // Find first link node
  const link = cell.children.find((n) => n.type === 'link') as { type: 'link'; url: string } | undefined;
  return { text, url: link?.url ?? null };
}

function parseTags(raw: string): string[] {
  return raw
    .split(/[,|]/)
    .map((t) => t.trim())
    .filter(Boolean);
}

function parseTableRows(table: Table, year: number, upcoming: boolean): Event[] {
  const events: Event[] = [];
  // Skip header row (index 0)
  for (let i = 1; i < table.children.length; i++) {
    const row = table.children[i];
    if (!row || row.children.length < 7) continue;

    const cells = row.children;
    const { text: name, url } = extractLink(cells[0]);
    if (!name) continue;

    const stateRaw = mdastToString(cells[1]).trim();
    const dateFromRaw = mdastToString(cells[2]).trim();
    const dateToRaw = mdastToString(cells[3]).trim();
    const cfpOpenRaw = mdastToString(cells[4]).trim();
    const cfpCloseRaw = mdastToString(cells[5]).trim();
    const tagsRaw = mdastToString(cells[6]).trim();

    events.push({
      name,
      url,
      state: normalizeState(stateRaw),
      stateRaw,
      dateFrom: parseEventDate(dateFromRaw),
      dateTo: parseEventDate(dateToRaw),
      dateFromRaw,
      dateToRaw,
      cfpOpen: parseEventDate(cfpOpenRaw),
      cfpClose: parseEventDate(cfpCloseRaw),
      cfpOpenRaw,
      cfpCloseRaw,
      tags: parseTags(tagsRaw),
      year,
      upcoming,
    });
  }
  return events;
}

function parseMarkdown(content: string): Root {
  return unified().use(remarkParse).use(remarkGfm).parse(content) as Root;
}

function getTablesFromAst(ast: Root): Table[] {
  return ast.children.filter((n) => n.type === 'table') as Table[];
}

export function parseUpcomingEvents(): Event[] {
  const content = readFileSync(join(DATA_ROOT, 'README.md'), 'utf-8');
  const ast = parseMarkdown(content);
  const tables = getTablesFromAst(ast);
  if (tables.length === 0) return [];
  // README has one upcoming events table
  const currentYear = new Date().getFullYear();
  return parseTableRows(tables[0], currentYear, true);
}

export function parsePastEvents(year: number): Event[] {
  const filePath = join(DATA_ROOT, 'Past Events', `${year}.md`);
  try {
    const content = readFileSync(filePath, 'utf-8');
    const ast = parseMarkdown(content);
    const tables = getTablesFromAst(ast);
    if (tables.length === 0) return [];
    return parseTableRows(tables[0], year, false);
  } catch {
    // No archive file for this year — fall back to README for the current year
    if (year === new Date().getFullYear()) {
      return parseUpcomingEvents().map((e) => ({ ...e, upcoming: false }));
    }
    return [];
  }
}

export function getAvailableYears(): number[] {
  try {
    const dir = join(DATA_ROOT, 'Past Events');
    const archivedYears = readdirSync(dir)
      .filter((f) => /^\d{4}\.md$/.test(f))
      .map((f) => parseInt(f, 10));

    const currentYear = new Date().getFullYear();
    const years = archivedYears.includes(currentYear)
      ? archivedYears
      : [currentYear, ...archivedYears];

    return years.sort((a, b) => b - a); // newest first
  } catch {
    return [new Date().getFullYear()];
  }
}

export function getAllEvents(): Event[] {
  const upcoming = parseUpcomingEvents();
  const years = getAvailableYears();
  const past = years.flatMap((y) => parsePastEvents(y));
  return [...upcoming, ...past];
}


================================================
FILE: website/src/data/parse-meetups.ts
================================================
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';
import { toString as mdastToString } from 'mdast-util-to-string';
import type { Root, Table, TableRow, Heading } from 'mdast';
import type { Meetup, StateCode } from './types.js';
import { normalizeState } from './normalize-state.js';

import { resolve } from 'path';
const DATA_ROOT = resolve(process.cwd(), '..');

const STATE_FILE_MAP: Record<string, StateCode> = {
  ACT: 'ACT', NSW: 'NSW', NT: 'NT', QLD: 'QLD',
  SA: 'SA', TAS: 'TAS', VIC: 'VIC', WA: 'WA',
};

function extractLink(cell: TableRow['children'][number]): { text: string; url: string | null } {
  const text = mdastToString(cell).trim();
  const link = cell.children.find((n) => n.type === 'link') as { type: 'link'; url: string } | undefined;
  return { text, url: link?.url ?? null };
}

function isRowEmpty(row: TableRow): boolean {
  return row.children.every((cell) => mdastToString(cell).trim() === '');
}

function parseMeetupsFromFile(stateCode: StateCode, content: string): Meetup[] {
  const ast = unified().use(remarkParse).use(remarkGfm).parse(content) as Root;
  const meetups: Meetup[] = [];
  let stale = false;

  for (const node of ast.children) {
    if (node.type === 'heading') {
      const headingText = mdastToString(node as Heading).toLowerCase();
      if (headingText.includes('stale')) {
        stale = true;
      }
      continue;
    }

    if (node.type !== 'table') continue;

    const table = node as Table;
    const headerRow = table.children[0];
    if (!headerRow) continue;
    const colCount = headerRow.children.length;

    for (let i = 1; i < table.children.length; i++) {
      const row = table.children[i];
      if (!row || isRowEmpty(row)) continue;

      const { text: name, url } = extractLink(row.children[0]);
      if (!name) continue;

      if (stale || colCount === 1) {
        // Stale meetups table — only name column
        meetups.push({
          name,
          url,
          location: '',
          host: '',
          frequency: '',
          tags: [],
          state: stateCode,
          stale: true,
        });
      } else {
        // Active meetups: Name | Location | Host | Frequency | Tags
        const location = colCount > 1 ? mdastToString(row.children[1]).trim() : '';
        const host = colCount > 2 ? mdastToString(row.children[2]).trim() : '';
        const frequency = colCount > 3 ? mdastToString(row.children[3]).trim() : '';
        const tagsRaw = colCount > 4 ? mdastToString(row.children[4]).trim() : '';
        const tags = tagsRaw
          .split(/[,|&]/)
          .map((t) => t.trim().replace(/^["']|["']$/g, ''))
          .filter(Boolean);

        meetups.push({
          name,
          url,
          location,
          host,
          frequency,
          tags,
          state: stateCode,
          stale: false,
        });
      }
    }
  }

  return meetups;
}

export function parseMeetupsByState(state: string): Meetup[] {
  const stateCode = STATE_FILE_MAP[state.toUpperCase()];
  if (!stateCode) return [];

  const filePath = join(DATA_ROOT, 'Meetups', `${state.toUpperCase()}.md`);
  try {
    const content = readFileSync(filePath, 'utf-8');
    return parseMeetupsFromFile(stateCode, content);
  } catch {
    return [];
  }
}

export function getAllMeetups(): Meetup[] {
  const dir = join(DATA_ROOT, 'Meetups');
  try {
    const files = readdirSync(dir).filter((f) => f.endsWith('.md'));
    return files.flatMap((f) => {
      const stateCode = f.replace('.md', '') as StateCode;
      const content = readFileSync(join(dir, f), 'utf-8');
      return parseMeetupsFromFile(stateCode, content);
    });
  } catch {
    return [];
  }
}

export function getAvailableStates(): StateCode[] {
  return Object.keys(STATE_FILE_MAP) as StateCode[];
}


================================================
FILE: website/src/data/types.ts
================================================
export type StateCode =
  | 'ACT' | 'NSW' | 'NT' | 'QLD' | 'SA' | 'TAS' | 'VIC' | 'WA'
  | 'ALL' | 'OTH' | 'VIRTUAL';

export interface Event {
  name: string;
  url: string | null;
  state: StateCode;
  stateRaw: string;
  dateFrom: Date | null;
  dateTo: Date | null;
  dateFromRaw: string;
  dateToRaw: string;
  cfpOpen: Date | null;
  cfpClose: Date | null;
  cfpOpenRaw: string;
  cfpCloseRaw: string;
  tags: string[];
  year: number;
  upcoming: boolean;
}

export interface Meetup {
  name: string;
  url: string | null;
  location: string;
  host: string;
  frequency: string;
  tags: string[];
  state: StateCode;
  stale: boolean;
}


================================================
FILE: website/src/env.d.ts
================================================
/// <reference types="astro/client" />
/// <reference path="../.astro/types.d.ts" />

================================================
FILE: website/src/layouts/Base.astro
================================================
---
import Nav from '@components/Nav.astro';
import Footer from '@components/Footer.astro';
import '../styles/global.css';

interface Props {
  title: string;
  description?: string;
}

const { title, description = 'A community-maintained list of Australian developer events and meetups.' } = Astro.props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
---

<!doctype html>
<html lang="en">
  <head>
    <script is:inline>
      (function() {
        var saved = localStorage.getItem('theme');
        var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
        document.documentElement.dataset.theme = saved || (prefersDark ? 'dark' : 'light');
      })();
    </script>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{title === 'DevEvents' ? title : `${title} | DevEvents`}</title>
    <meta name="description" content={description} />
    <link rel="canonical" href={canonicalURL} />

    <!-- Open Graph -->
    <meta property="og:type" content="website" />
    <meta property="og:url" content={canonicalURL} />
    <meta property="og:title" content={title} />
    <meta property="og:description" content={description} />
    <meta property="og:site_name" content="DevEvents" />

    <!-- Favicon -->
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
  </head>
  <body>
    <Nav currentPath={Astro.url.pathname} />
    <main>
      <slot />
    </main>
    <Footer />
  </body>
</html>


================================================
FILE: website/src/pages/404.astro
================================================
---
import Base from '@layouts/Base.astro';
---

<Base title="Page Not Found">
  <div class="container">
    <div style="text-align:center; padding: 5rem 1rem;">
      <div style="font-size:4rem; font-weight:800; color:var(--color-border); line-height:1;">404</div>
      <h1 style="font-size:1.5rem; margin:0.5rem 0 1rem;">Page not found</h1>
      <p style="color:var(--color-text-muted); margin-bottom:2rem;">
        This page doesn't exist. Try browsing events or meetups instead.
      </p>
      <div style="display:flex; gap:0.75rem; justify-content:center; flex-wrap:wrap;">
        <a href="/" style="padding:0.55rem 1.25rem; background:var(--color-accent); color:#fff; border-radius:var(--radius); font-weight:600;">
          Go home
        </a>
        <a href="/events" style="padding:0.55rem 1.25rem; border:1.5px solid var(--color-border); border-radius:var(--radius); font-weight:600; color:var(--color-text);">
          Browse events
        </a>
      </div>
    </div>
  </div>
</Base>


================================================
FILE: website/src/pages/about.astro
================================================
---
import Base from '@layouts/Base.astro';
---

<Base
  title="About"
  description="About DevEvents — a community-maintained list of Australian developer events."
>
  <div class="container">
    <div class="page-header">
      <h1>About DevEvents</h1>
    </div>
    <div class="page-content" style="max-width:680px;">
      <p>
        DevEvents is a community-maintained, open-source list of Australian developer events — conferences, workshops, and meetups of interest to software developers.
      </p>
      <p>
        The data lives in plain Markdown files on GitHub. Anyone can contribute by opening a pull request, or even editing a file directly in the browser without any git knowledge.
      </p>

      <h2 style="font-size:1.2rem; margin-top:2rem;">What gets listed?</h2>
      <ul>
        <li>Australian-based events from 2016 onwards</li>
        <li>Events of interest to the software development community (technical, leadership, agile, AI, etc.)</li>
        <li>In-person and virtual events; free and paid</li>
      </ul>

      <h2 style="font-size:1.2rem; margin-top:2rem;">How to contribute</h2>
      <p>
        The simplest way is to use the GitHub editor:
      </p>
      <ol>
        <li>Open <a href="https://github.com/vatsalyagoel/DevEvents/edit/main/README.md" target="_blank" rel="noopener noreferrer">README.md on GitHub</a></li>
        <li>Add your event to the table in chronological order</li>
        <li>Submit a pull request</li>
      </ol>
      <p>
        See the full <a href="https://github.com/vatsalyagoel/DevEvents/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer">contribution guide</a> for formatting rules and date format requirements.
      </p>

      <h2 style="font-size:1.2rem; margin-top:2rem;">License</h2>
      <p>
        All data is released under <a href="https://creativecommons.org/publicdomain/zero/1.0/" target="_blank" rel="noopener noreferrer">Creative Commons CC0</a> — you are free to use it for any purpose.
      </p>

      <p style="margin-top:2rem;">
        <a href="https://github.com/vatsalyagoel/DevEvents" target="_blank" rel="noopener noreferrer">View on GitHub &rarr;</a>
      </p>
    </div>
  </div>
</Base>


================================================
FILE: website/src/pages/events/index.astro
================================================
---
import Base from '@layouts/Base.astro';
import FilterIsland from '@components/FilterIsland';
import { parseUpcomingEvents } from '@data/parse-events';

const allUpcoming = parseUpcomingEvents();
const today = new Date();
today.setHours(0, 0, 0, 0);
const upcoming = allUpcoming.filter((e) => {
  const end = e.dateTo ?? e.dateFrom;
  if (!end) return true;
  return end >= today;
});
---

<Base
  title="Upcoming Events"
  description={`${upcoming.length} upcoming Australian developer events. Filter by state, topic, and CFP status.`}
>
  <div class="container">
    <div class="page-header">
      <div class="breadcrumb">
        <span>Events</span>
      </div>
      <div style="display:flex; align-items:flex-start; justify-content:space-between; flex-wrap:wrap; gap:0.75rem;">
        <div>
          <h1 style="margin:0">Upcoming Events</h1>
          <p style="color: var(--color-text-muted); margin: 0.4rem 0 0;">
            <a href="/events/past">Browse past events &rarr;</a>
          </p>
        </div>
        <a
          href="https://github.com/vatsalyagoel/DevEvents/edit/main/README.md"
          target="_blank"
          rel="noopener noreferrer"
          style="font-size:0.83rem; color:var(--color-text-muted); border:1px solid var(--color-border); border-radius:var(--radius); padding:0.3rem 0.75rem; white-space:nowrap;"
        >
          Edit on GitHub
        </a>
      </div>
    </div>
    <div class="page-content">
      {upcoming.length === 0 ? (
        <div class="empty-state">
          <p>No upcoming events listed yet.</p>
          <p>
            <a
              href="https://github.com/vatsalyagoel/DevEvents/edit/main/README.md"
              target="_blank"
              rel="noopener noreferrer"
            >
              Add the first one &rarr;
            </a>
          </p>
        </div>
      ) : (
        <FilterIsland events={upcoming} client:load />
      )}
    </div>
  </div>
</Base>


================================================
FILE: website/src/pages/events/past/[year].astro
================================================
---
import Base from '@layouts/Base.astro';
import EventTable from '@components/EventTable.astro';
import { getAvailableYears, parsePastEvents } from '@data/parse-events';

export async function getStaticPaths() {
  const years = getAvailableYears();
  return years.map((year) => ({
    params: { year: String(year) },
    props: { year, events: parsePastEvents(year) },
  }));
}

interface Props {
  year: number;
  events: Awaited<ReturnType<typeof parsePastEvents>>;
}

const { year } = Astro.props;
let { events } = Astro.props;
const allYears = getAvailableYears();
const currentYear = new Date().getFullYear();
if (year === currentYear) {
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  events = events.filter((e) => {
    const end = e.dateTo ?? e.dateFrom;
    return end !== null && end < today;
  });
}
const currentIndex = allYears.indexOf(year);
const prevYear = allYears[currentIndex + 1];
const nextYear = allYears[currentIndex - 1];
const editUrl = year === currentYear
  ? 'https://github.com/vatsalyagoel/DevEvents/edit/main/README.md'
  : `https://github.com/vatsalyagoel/DevEvents/edit/main/Past%20Events/${year}.md`;
---

<Base
  title={`${year} Events`}
  description={`Australian developer events in ${year}. ${events.length} events listed.`}
>
  <div class="container">
    <div class="page-header">
      <div class="breadcrumb">
        <a href="/events">Events</a>
        <span class="breadcrumb__sep">/</span>
        <a href="/events/past">Past Events</a>
        <span class="breadcrumb__sep">/</span>
        <span>{year}</span>
      </div>
      <div style="display:flex; align-items:flex-start; justify-content:space-between; flex-wrap:wrap; gap:0.75rem;">
        <div>
          <h1 style="margin:0">{year} Events</h1>
          <p style="color: var(--color-text-muted); margin: 0.4rem 0 0;">
            {events.length} event{events.length !== 1 ? 's' : ''} listed
          </p>
        </div>
        <a
          href={editUrl}
          target="_blank"
          rel="noopener noreferrer"
          style="font-size:0.83rem; color:var(--color-text-muted); border:1px solid var(--color-border); border-radius:var(--radius); padding:0.3rem 0.75rem; white-space:nowrap;"
        >
          Edit on GitHub
        </a>
      </div>
    </div>
    <div class="page-content">
      <EventTable events={events} />

      <div style="display:flex; gap:1rem; margin-top:2rem; font-size:0.9rem; flex-wrap:wrap;">
        {prevYear && <a href={`/events/past/${prevYear}`}>&larr; {prevYear}</a>}
        {nextYear && <a href={`/events/past/${nextYear}`}>{nextYear} &rarr;</a>}
      </div>
    </div>
  </div>
</Base>


================================================
FILE: website/src/pages/events/past/index.astro
================================================
---
import Base from '@layouts/Base.astro';
import { getAvailableYears } from '@data/parse-events';

const years = getAvailableYears();
---

<Base title="Past Events" description="Archive of Australian developer events from 2016 to present.">
  <div class="container">
    <div class="page-header">
      <div class="breadcrumb">
        <a href="/events">Events</a>
        <span class="breadcrumb__sep">/</span>
        <span>Past Events</span>
      </div>
      <h1>Past Events</h1>
      <p style="color: var(--color-text-muted); margin: 0.5rem 0 0;">Australian developer events archived by year.</p>
    </div>
    <div class="page-content">
      <div class="year-grid">
        {years.map((year) => (
          <a href={`/events/past/${year}`} class="year-card">{year}</a>
        ))}
      </div>
    </div>
  </div>
</Base>


================================================
FILE: website/src/pages/index.astro
================================================
---
import Base from '@layouts/Base.astro';
import UpcomingEventsIsland from '@components/UpcomingEventsIsland';
import { parseUpcomingEvents } from '@data/parse-events';
import { getAvailableStates, parseMeetupsByState } from '@data/parse-meetups';

const allUpcoming = parseUpcomingEvents();
const today = new Date();
today.setHours(0, 0, 0, 0);
const upcoming = allUpcoming.filter((e) => {
  const end = e.dateTo ?? e.dateFrom;
  if (!end) return true;
  return end >= today;
});

const states = getAvailableStates();
const meetupCounts = states.map((s) => ({
  state: s,
  count: parseMeetupsByState(s).filter((m) => !m.stale).length,
})).filter(({ count }) => count > 0);
---

<Base
  title="DevEvents"
  description="A community-maintained list of Australian developer events and meetups."
>
  <!-- Hero -->
  <section class="hero">
    <div class="container">
      <h1 class="hero__title">
        Australian<br/><span>Developer Events</span>
      </h1>
      <p class="hero__sub">
        A community-maintained, open-source list of conferences, workshops, and meetups for software developers in Australia.
      </p>
      <div style="display:flex; gap:0.75rem; justify-content:center; flex-wrap:wrap;">
        <a href="/events" style="padding:0.65rem 1.5rem; background:var(--color-accent); color:#fff; border-radius:var(--radius); font-weight:600; font-size:0.95rem;">
          Browse Events
        </a>
        <a href="https://github.com/vatsalyagoel/DevEvents/edit/main/README.md" target="_blank" rel="noopener noreferrer"
           style="padding:0.65rem 1.5rem; border:1.5px solid var(--color-border); border-radius:var(--radius); font-weight:600; font-size:0.95rem; color:var(--color-text);">
          Add an Event
        </a>
      </div>
    </div>
  </section>

  <!-- Upcoming events -->
  <section style="padding-bottom: 3rem;">
    <div class="container">
      <div style="display:flex; align-items:baseline; justify-content:space-between; flex-wrap:wrap; gap:0.5rem; margin-bottom:1.25rem;">
        <h2 class="section-heading" style="margin:0">Upcoming Events</h2>
        <a href="/events" style="font-size:0.9rem;">View all &rarr;</a>
      </div>

      <UpcomingEventsIsland events={upcoming} client:load />
    </div>
  </section>

  <!-- Meetups teaser -->
  {meetupCounts.length > 0 && (
    <section style="background:var(--color-surface); border-top:1px solid var(--color-border); border-bottom:1px solid var(--color-border); padding:2.5rem 0;">
      <div class="container">
        <div style="display:flex; align-items:baseline; justify-content:space-between; flex-wrap:wrap; gap:0.5rem; margin-bottom:1.25rem;">
          <h2 class="section-heading" style="margin:0">Regular Meetups</h2>
          <a href="/meetups" style="font-size:0.9rem;">All meetups &rarr;</a>
        </div>
        <div style="display:flex; flex-wrap:wrap; gap:0.6rem;">
          {meetupCounts.map(({ state, count }) => (
            <a
              href={`/meetups/${state.toLowerCase()}`}
              style="display:inline-flex; align-items:center; gap:0.4rem; padding:0.4rem 0.9rem; background:var(--color-bg); border:1px solid var(--color-border); border-radius:var(--radius); font-size:0.88rem; color:var(--color-text);"
            >
              <span class="state-pill" data-state={state}>{state}</span>
              {count} meetup{count !== 1 ? 's' : ''}
            </a>
          ))}
        </div>
      </div>
    </section>
  )}

  <!-- Contribute CTA -->
  <section style="padding: 3.5rem 0;">
    <div class="container" style="text-align:center;">
      <h2 style="font-size:1.4rem; font-weight:700; margin-bottom:0.75rem;">Know of an event we missed?</h2>
      <p style="color:var(--color-text-muted); margin-bottom:1.5rem; max-width:480px; margin-left:auto; margin-right:auto;">
        This list is maintained by the community. You can add or update events directly on GitHub — no coding required.
      </p>
      <a
        href="https://github.com/vatsalyagoel/DevEvents"
        target="_blank"
        rel="noopener noreferrer"
        style="padding:0.65rem 1.5rem; background:var(--color-accent); color:#fff; border-radius:var(--radius); font-weight:600;"
      >
        Contribute on GitHub
      </a>
    </div>
  </section>
</Base>


================================================
FILE: website/src/pages/meetups/[state].astro
================================================
---
import Base from '@layouts/Base.astro';
import { getAvailableStates, parseMeetupsByState } from '@data/parse-meetups';
import type { StateCode } from '@data/types';

export async function getStaticPaths() {
  const states = getAvailableStates();
  return states.map((state) => ({
    params: { state: state.toLowerCase() },
    props: { state, meetups: parseMeetupsByState(state) },
  }));
}

interface Props {
  state: StateCode;
  meetups: Awaited<ReturnType<typeof parseMeetupsByState>>;
}

const STATE_NAMES: Record<StateCode, string> = {
  ACT: 'Australian Capital Territory',
  NSW: 'New South Wales',
  NT: 'Northern Territory',
  QLD: 'Queensland',
  SA: 'South Australia',
  TAS: 'Tasmania',
  VIC: 'Victoria',
  WA: 'Western Australia',
  ALL: 'All States',
  OTH: 'Other',
  VIRTUAL: 'Virtual',
};

const { state, meetups } = Astro.props;
const active = meetups.filter((m) => !m.stale);
const stale = meetups.filter((m) => m.stale);
const stateName = STATE_NAMES[state] ?? state;
const editUrl = `https://github.com/vatsalyagoel/DevEvents/edit/main/Meetups/${state}.md`;
---

<Base
  title={`${state} Meetups`}
  description={`Developer meetups in ${stateName}. ${active.length} active meetup${active.length !== 1 ? 's' : ''} listed.`}
>
  <div class="container">
    <div class="page-header">
      <div class="breadcrumb">
        <a href="/meetups">Meetups</a>
        <span class="breadcrumb__sep">/</span>
        <span>{state}</span>
      </div>
      <div style="display:flex; align-items:flex-start; justify-content:space-between; flex-wrap:wrap; gap:0.75rem;">
        <h1 style="margin:0">
          <span class="state-pill" data-state={state} style="font-size:0.75rem;vertical-align:middle">{state}</span>
          &nbsp;{stateName} Meetups
        </h1>
        <a
          href={editUrl}
          target="_blank"
          rel="noopener noreferrer"
          style="font-size:0.83rem; color:var(--color-text-muted); border:1px solid var(--color-border); border-radius:var(--radius); padding:0.3rem 0.75rem; white-space:nowrap;"
        >
          Edit on GitHub
        </a>
      </div>
    </div>
    <div class="page-content">
      {active.length === 0 ? (
        <div class="empty-state">
          <p>No meetups currently listed for {stateName}.</p>
          <p>
            <a href={editUrl} target="_blank" rel="noopener noreferrer">
              Add one on GitHub &rarr;
            </a>
          </p>
        </div>
      ) : (
        <div class="data-table__wrap" style="margin-bottom: 2rem;">
          <table class="data-table">
            <thead>
              <tr>
                <th>Meetup</th>
                <th>Location</th>
                <th>Host</th>
                <th>Frequency</th>
                <th>Tags</th>
              </tr>
            </thead>
            <tbody>
              {active.map((m) => (
                <tr>
                  <td>
                    {m.url
                      ? <a href={m.url} target="_blank" rel="noopener noreferrer">{m.name}</a>
                      : m.name
                    }
                  </td>
                  <td>{m.location}</td>
                  <td>{m.host}</td>
                  <td>{m.frequency}</td>
                  <td>
                    <div style="display:flex;flex-wrap:wrap;gap:0.3rem">
                      {m.tags.map((t) => <span class="tag-chip">{t}</span>)}
                    </div>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}

      {stale.length > 0 && (
        <details>
          <summary style="cursor:pointer; color: var(--color-text-muted); font-size:0.9rem; margin-bottom:1rem;">
            {stale.length} stale meetup{stale.length !== 1 ? 's' : ''} (no longer running)
          </summary>
          <div class="data-table__wrap" style="margin-top:0.75rem;">
            <table class="data-table">
              <thead>
                <tr><th>Meetup</th></tr>
              </thead>
              <tbody>
                {stale.map((m) => (
                  <tr>
                    <td>
                      {m.url
                        ? <a href={m.url} target="_blank" rel="noopener noreferrer">{m.name}</a>
                        : m.name
                      }
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </details>
      )}
    </div>
  </div>
</Base>


================================================
FILE: website/src/pages/meetups/index.astro
================================================
---
import Base from '@layouts/Base.astro';
import { getAvailableStates, parseMeetupsByState } from '@data/parse-meetups';
import type { StateCode } from '@data/types';

const STATE_NAMES: Record<StateCode, string> = {
  ACT: 'Australian Capital Territory',
  NSW: 'New South Wales',
  NT: 'Northern Territory',
  QLD: 'Queensland',
  SA: 'South Australia',
  TAS: 'Tasmania',
  VIC: 'Victoria',
  WA: 'Western Australia',
  ALL: 'All States',
  OTH: 'Other',
  VIRTUAL: 'Virtual',
};

const states = getAvailableStates();
const stateData = states.map((state) => {
  const meetups = parseMeetupsByState(state);
  const active = meetups.filter((m) => !m.stale);
  return { state, name: STATE_NAMES[state] ?? state, count: active.length };
});
---

<Base
  title="Meetups"
  description="Regular developer meetups across Australian states and territories."
>
  <div class="container">
    <div class="page-header">
      <div class="breadcrumb">
        <span>Meetups</span>
      </div>
      <h1>Meetups</h1>
      <p style="color: var(--color-text-muted); margin: 0.5rem 0 0;">
        Regular developer meetups by state. Want to add one?
        <a href="https://github.com/vatsalyagoel/DevEvents" target="_blank" rel="noopener noreferrer">Contribute on GitHub &rarr;</a>
      </p>
    </div>
    <div class="page-content">
      <div class="state-grid">
        {stateData.map(({ state, name, count }) => (
          <a href={`/meetups/${state.toLowerCase()}`} class="state-card">
            <div class="state-card__name">
              <span class="state-pill" data-state={state}>{state}</span>
              &nbsp;{name}
            </div>
            <div class="state-card__count">
              {count > 0 ? `${count} active meetup${count !== 1 ? 's' : ''}` : 'No meetups listed'}
            </div>
          </a>
        ))}
      </div>
    </div>
  </div>
</Base>


================================================
FILE: website/src/styles/global.css
================================================
:root {
  --color-bg: #fafafa;
  --color-surface: #ffffff;
  --color-border: #e5e7eb;
  --color-text: #111827;
  --color-text-muted: #6b7280;
  --color-accent: #0c7c5f;
  --color-accent-light: #ecfdf5;
  --color-accent-hover: #096649;
  --color-subtle: #f9fafb;
  --color-tag-bg: #f3f4f6;

  /* State colours */
  --state-act: #7c3aed;
  --state-nsw: #1d4ed8;
  --state-nt: #b45309;
  --state-qld: #b91c1c;
  --state-sa: #0369a1;
  --state-tas: #065f46;
  --state-vic: #0c7c5f;
  --state-wa: #c2410c;
  --state-all: #4b5563;
  --state-oth: #9ca3af;
  --state-virtual: #6d28d9;

  --radius: 8px;
  --shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);
  --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.08), 0 2px 4px -2px rgba(0,0,0,0.05);

  --font: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace;
}

*, *::before, *::after { box-sizing: border-box; }

html { font-size: 16px; }

body {
  margin: 0;
  font-family: var(--font);
  background: var(--color-bg);
  color: var(--color-text);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--color-accent); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3, h4 { line-height: 1.2; margin: 0 0 0.5em; }

.container {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 1.5rem;
}

/* State pill */
.state-pill {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 9999px;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #fff;
  background: var(--state-oth);
}
.state-pill[data-state="ACT"] { background: var(--state-act); }
.state-pill[data-state="NSW"] { background: var(--state-nsw); }
.state-pill[data-state="NT"]  { background: var(--state-nt); }
.state-pill[data-state="QLD"] { background: var(--state-qld); }
.state-pill[data-state="SA"]  { background: var(--state-sa); }
.state-pill[data-state="TAS"] { background: var(--state-tas); }
.state-pill[data-state="VIC"] { background: var(--state-vic); }
.state-pill[data-state="WA"]  { background: var(--state-wa); }
.state-pill[data-state="ALL"] { background: var(--state-all); }
.state-pill[data-state="VIRTUAL"] { background: var(--state-virtual); }

/* Tag chip */
.tag-chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 9999px;
  font-size: 0.72rem;
  background: var(--color-tag-bg);
  color: var(--color-text-muted);
  border: 1px solid var(--color-border);
}

/* Event card */
.event-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1.25rem 1.5rem;
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
}
.event-card:hover {
  box-shadow: var(--shadow-md);
  border-color: var(--color-border);
}
.event-card__header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.event-card__name {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--color-text);
}
.event-card__name a { color: var(--color-text); }
.event-card__name a:hover { color: var(--color-accent); text-decoration: none; }
.event-card__date {
  font-size: 0.9rem;
  color: var(--color-text-muted);
}
.event-card__cfp {
  font-size: 0.82rem;
  color: var(--color-text-muted);
}
.event-card__cfp .cfp-open {
  color: var(--color-accent);
  font-weight: 600;
}
.event-card__cfp .cfp-closed {
  color: var(--color-text-muted);
}
.event-card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.25rem;
}

/* Grid of cards */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1.25rem;
}

/* Tables */
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}
.data-table th {
  text-align: left;
  padding: 0.6rem 0.75rem;
  background: var(--color-subtle);
  border-bottom: 2px solid var(--color-border);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-text-muted);
}
.data-table td {
  padding: 0.65rem 0.75rem;
  border-bottom: 1px solid var(--color-border);
  vertical-align: top;
}
.data-table tr:last-child td { border-bottom: none; }
.data-table tr:hover td { background: var(--color-subtle); }
.data-table__wrap {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
  overflow-x: auto;
}

/* Nav */
.nav {
  background: var(--color-surface);
  border-bottom: 1px solid var(--color-border);
  position: sticky;
  top: 0;
  z-index: 100;
}
.nav__inner {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  height: 56px;
}
.nav__logo {
  font-weight: 800;
  font-size: 1.1rem;
  color: var(--color-accent);
  letter-spacing: -0.02em;
  flex-shrink: 0;
}
.nav__logo:hover { text-decoration: none; }
.nav__links {
  display: flex;
  gap: 1.25rem;
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1;
}
.nav__links a {
  font-size: 0.9rem;
  color: var(--color-text-muted);
  font-weight: 500;
}
.nav__links a:hover, .nav__links a.active { color: var(--color-text); text-decoration: none; }
.nav__cta {
  padding: 0.4rem 0.9rem;
  background: var(--color-accent);
  color: #fff !important;
  border-radius: var(--radius);
  font-size: 0.85rem;
  font-weight: 600;
  white-space: nowrap;
}
.nav__cta:hover { background: var(--color-accent-hover) !important; text-decoration: none !important; }

/* Hero */
.hero {
  padding: 4rem 0 3rem;
  text-align: center;
}
.hero__title {
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  margin-bottom: 0.75rem;
}
.hero__title span { color: var(--color-accent); }
.hero__sub {
  font-size: 1.1rem;
  color: var(--color-text-muted);
  max-width: 520px;
  margin: 0 auto 2rem;
}

/* Section headings */
.section-heading {
  font-size: 1.4rem;
  font-weight: 700;
  margin-bottom: 1.25rem;
  letter-spacing: -0.01em;
}

/* Empty state */
.empty-state {
  text-align: center;
  padding: 3rem 1rem;
  color: var(--color-text-muted);
}

/* Footer */
.footer {
  border-top: 1px solid var(--color-border);
  padding: 2rem 0;
  margin-top: 4rem;
  font-size: 0.85rem;
  color: var(--color-text-muted);
}
.footer__inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 1rem;
}
.footer a { color: var(--color-text-muted); }
.footer a:hover { color: var(--color-text); }

/* Year grid */
.year-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 1rem;
}
.year-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1.5rem 1rem;
  text-align: center;
  box-shadow: var(--shadow);
  color: var(--color-text);
  font-weight: 700;
  font-size: 1.3rem;
  transition: box-shadow 0.15s, border-color 0.15s;
}
.year-card:hover {
  box-shadow: var(--shadow-md);
  border-color: var(--color-accent);
  text-decoration: none;
  color: var(--color-accent);
}

/* State overview grid */
.state-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1rem;
}
.state-card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1.25rem;
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  transition: box-shadow 0.15s, border-color 0.15s;
  color: var(--color-text);
}
.state-card:hover {
  box-shadow: var(--shadow-md);
  border-color: var(--color-accent);
  text-decoration: none;
}
.state-card__name { font-weight: 700; font-size: 1.1rem; }
.state-card__count { font-size: 0.85rem; color: var(--color-text-muted); }

/* Breadcrumb */
.breadcrumb {
  font-size: 0.85rem;
  color: var(--color-text-muted);
  margin-bottom: 1.5rem;
  display: flex;
  gap: 0.4rem;
  align-items: center;
  flex-wrap: wrap;
}
.breadcrumb a { color: var(--color-text-muted); }
.breadcrumb a:hover { color: var(--color-text); }
.breadcrumb__sep { color: var(--color-border); }

/* Page layout */
.page-header {
  padding: 2.5rem 0 1.5rem;
}
.page-header h1 {
  font-size: clamp(1.5rem, 3vw, 2rem);
  font-weight: 800;
  letter-spacing: -0.02em;
}
.page-content {
  padding-bottom: 3rem;
}

@media (max-width: 640px) {
  .nav__links { gap: 0.75rem; }
  .card-grid { grid-template-columns: 1fr; }
  .footer__inner { flex-direction: column; text-align: center; }
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-bg: #111827;
    --color-surface: #1f2937;
    --color-border: #374151;
    --color-text: #f9fafb;
    --color-text-muted: #9ca3af;
    --color-accent: #10b981;
    --color-accent-light: #064e3b;
    --color-accent-hover: #34d399;
    --color-subtle: #1f2937;
    --color-tag-bg: #374151;
  }
}
[data-theme="dark"] {
  --color-bg: #111827;
  --color-surface: #1f2937;
  --color-border: #374151;
  --color-text: #f9fafb;
  --color-text-muted: #9ca3af;
  --color-accent: #10b981;
  --color-accent-light: #064e3b;
  --color-accent-hover: #34d399;
  --color-subtle: #1f2937;
  --color-tag-bg: #374151;
}

/* Theme toggle button */
.theme-toggle {
  background: none;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  cursor: pointer;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1rem;
  color: var(--color-text-muted);
  flex-shrink: 0;
  padding: 0;
  transition: border-color 0.15s, color 0.15s;
}
.theme-toggle:hover { border-color: var(--color-accent); color: var(--color-accent); }
[data-theme="dark"] .theme-toggle::after,
:root:not([data-theme]) .theme-toggle::after { content: '☀'; }
[data-theme="light"] .theme-toggle::after { content: '☾'; }
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .theme-toggle::after { content: '☾'; }
}


================================================
FILE: website/tsconfig.json
================================================
{
  "extends": "astro/tsconfigs/strict",
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact",
    "baseUrl": ".",
    "paths": {
      "@data/*": ["src/data/*"],
      "@components/*": ["src/components/*"],
      "@layouts/*": ["src/layouts/*"]
    }
  }
}
Download .txt
gitextract_z1vav1gl/

├── .github/
│   └── workflows/
│       ├── deploy.yml
│       └── pr-preview.yml
├── CLAUDE.md
├── CONTRIBUTING.md
├── FAQ.md
├── LICENSE.md
├── Meetups/
│   ├── ACT.md
│   ├── NSW.md
│   ├── NT.md
│   ├── QLD.md
│   ├── SA.md
│   ├── TAS.md
│   ├── VIC.md
│   └── WA.md
├── Past Events/
│   ├── 2016.md
│   ├── 2017.md
│   ├── 2018.md
│   ├── 2019.md
│   ├── 2020.md
│   ├── 2022.md
│   ├── 2024.md
│   ├── 2025.md
│   └── OTHER.md
├── README.md
└── website/
    ├── .gitignore
    ├── astro.config.mjs
    ├── package.json
    ├── public/
    │   └── robots.txt
    ├── src/
    │   ├── components/
    │   │   ├── EventCard.astro
    │   │   ├── EventTable.astro
    │   │   ├── FilterIsland.tsx
    │   │   ├── Footer.astro
    │   │   ├── Nav.astro
    │   │   └── UpcomingEventsIsland.tsx
    │   ├── data/
    │   │   ├── normalize-state.ts
    │   │   ├── parse-date.ts
    │   │   ├── parse-events.ts
    │   │   ├── parse-meetups.ts
    │   │   └── types.ts
    │   ├── env.d.ts
    │   ├── layouts/
    │   │   └── Base.astro
    │   ├── pages/
    │   │   ├── 404.astro
    │   │   ├── about.astro
    │   │   ├── events/
    │   │   │   ├── index.astro
    │   │   │   └── past/
    │   │   │       ├── [year].astro
    │   │   │       └── index.astro
    │   │   ├── index.astro
    │   │   └── meetups/
    │   │       ├── [state].astro
    │   │       └── index.astro
    │   └── styles/
    │       └── global.css
    └── tsconfig.json
Download .txt
SYMBOL INDEX (36 symbols across 7 files)

FILE: website/src/components/FilterIsland.tsx
  type Props (line 4) | interface Props {
  constant STATE_LABELS (line 8) | const STATE_LABELS: Record<string, string> = {
  function toDate (line 15) | function toDate(val: unknown): Date | null {
  function fmt (line 22) | function fmt(val: unknown): string {
  function fuzzyMatch (line 28) | function fuzzyMatch(query: string, target: string): boolean {
  function FilterIsland (line 38) | function FilterIsland({ events }: Props) {

FILE: website/src/components/UpcomingEventsIsland.tsx
  type Props (line 3) | interface Props {
  function toDate (line 7) | function toDate(val: unknown): Date | null {
  function fmt (line 14) | function fmt(val: unknown): string {
  function UpcomingEventsIsland (line 20) | function UpcomingEventsIsland({ events }: Props) {

FILE: website/src/data/normalize-state.ts
  constant STATE_MAP (line 3) | const STATE_MAP: Record<string, StateCode> = {
  function normalizeState (line 24) | function normalizeState(raw: string): StateCode {

FILE: website/src/data/parse-date.ts
  constant MONTH_MAP (line 1) | const MONTH_MAP: Record<string, number> = {
  function parseEventDate (line 6) | function parseEventDate(raw: string): Date | null {
  function formatDate (line 33) | function formatDate(date: Date): string {

FILE: website/src/data/parse-events.ts
  constant DATA_ROOT (line 13) | const DATA_ROOT = resolve(process.cwd(), '..');
  function extractLink (line 15) | function extractLink(cell: TableRow['children'][number]): { text: string...
  function parseTags (line 22) | function parseTags(raw: string): string[] {
  function parseTableRows (line 29) | function parseTableRows(table: Table, year: number, upcoming: boolean): ...
  function parseMarkdown (line 68) | function parseMarkdown(content: string): Root {
  function getTablesFromAst (line 72) | function getTablesFromAst(ast: Root): Table[] {
  function parseUpcomingEvents (line 76) | function parseUpcomingEvents(): Event[] {
  function parsePastEvents (line 86) | function parsePastEvents(year: number): Event[] {
  function getAvailableYears (line 103) | function getAvailableYears(): number[] {
  function getAllEvents (line 121) | function getAllEvents(): Event[] {

FILE: website/src/data/parse-meetups.ts
  constant DATA_ROOT (line 12) | const DATA_ROOT = resolve(process.cwd(), '..');
  constant STATE_FILE_MAP (line 14) | const STATE_FILE_MAP: Record<string, StateCode> = {
  function extractLink (line 19) | function extractLink(cell: TableRow['children'][number]): { text: string...
  function isRowEmpty (line 25) | function isRowEmpty(row: TableRow): boolean {
  function parseMeetupsFromFile (line 29) | function parseMeetupsFromFile(stateCode: StateCode, content: string): Me...
  function parseMeetupsByState (line 97) | function parseMeetupsByState(state: string): Meetup[] {
  function getAllMeetups (line 110) | function getAllMeetups(): Meetup[] {
  function getAvailableStates (line 124) | function getAvailableStates(): StateCode[] {

FILE: website/src/data/types.ts
  type StateCode (line 1) | type StateCode =
  type Event (line 5) | interface Event {
  type Meetup (line 23) | interface Meetup {
Condensed preview — 51 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (117K chars).
[
  {
    "path": ".github/workflows/deploy.yml",
    "chars": 1021,
    "preview": "name: Build and Deploy\n\non:\n  push:\n    branches: [main]\n    paths:\n      - 'README.md'\n      - 'Past Events/**'\n      -"
  },
  {
    "path": ".github/workflows/pr-preview.yml",
    "chars": 604,
    "preview": "name: PR Build Check\n\non:\n  pull_request:\n    branches: [main]\n    paths:\n      - 'README.md'\n      - 'Past Events/**'\n "
  },
  {
    "path": "CLAUDE.md",
    "chars": 1911,
    "preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1779,
    "preview": "\n# Australian Developer Events\n\n## Contributing\n\nYou want to help? Great!! Here's what you need to know.\n\n### How to con"
  },
  {
    "path": "FAQ.md",
    "chars": 1616,
    "preview": "\n# Australian Developer Events\n\n## Frequently Asked Questions - FAQ\n\n### My event is not listed/wrongly classified/there"
  },
  {
    "path": "LICENSE.md",
    "chars": 6966,
    "preview": "# Creative Commons\n\n## CC0 1.0 Universal\n\nCREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERV"
  },
  {
    "path": "Meetups/ACT.md",
    "chars": 194,
    "preview": "\n# Australian Developer Events\n\n## Australian Capital Territory Meetups\n\n| Event Name | Location | Host | Frequency | Ta"
  },
  {
    "path": "Meetups/NSW.md",
    "chars": 181,
    "preview": "\n# Australian Developer Events\n\n## New South Wales Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ------"
  },
  {
    "path": "Meetups/NT.md",
    "chars": 184,
    "preview": "\n# Australian Developer Events\n\n## Northern Territory Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---"
  },
  {
    "path": "Meetups/QLD.md",
    "chars": 300,
    "preview": "\n# Australian Developer Events\n\n## Queensland Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---------- "
  },
  {
    "path": "Meetups/SA.md",
    "chars": 1028,
    "preview": "\n# Australian Developer Events\n\n## South Australian Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| -----"
  },
  {
    "path": "Meetups/TAS.md",
    "chars": 175,
    "preview": "\n# Australian Developer Events\n\n## Tasmanian Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---------- |"
  },
  {
    "path": "Meetups/VIC.md",
    "chars": 2662,
    "preview": "\n# Australian Developer Events\n\n# Victorian Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---------- | "
  },
  {
    "path": "Meetups/WA.md",
    "chars": 184,
    "preview": "\n# Australian Developer Events\n\n## Western Australian Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---"
  },
  {
    "path": "Past Events/2016.md",
    "chars": 329,
    "preview": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2016\n\n| Event Name | State | Date From | Date To | Tags |\n| ------"
  },
  {
    "path": "Past Events/2017.md",
    "chars": 4626,
    "preview": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2017\n\n| Event Name | State | Date From | Date To | Tags |\n| ------"
  },
  {
    "path": "Past Events/2018.md",
    "chars": 2796,
    "preview": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2018\n\n| Event Name | State | Date From | Date To | CFP Open | CFP "
  },
  {
    "path": "Past Events/2019.md",
    "chars": 9215,
    "preview": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2019\n\n| Event Name | State | Date From | Date To | CFP Open | CFP "
  },
  {
    "path": "Past Events/2020.md",
    "chars": 4081,
    "preview": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2020\n\n| Event Name | State | Date From | Date To | CFP Open | CFP "
  },
  {
    "path": "Past Events/2022.md",
    "chars": 3229,
    "preview": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2022\n\n| Event Name | State | Date From | Date To | CFP Open | CFP "
  },
  {
    "path": "Past Events/2024.md",
    "chars": 2486,
    "preview": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2024\n\n| Event Name | State | Date From | Date To | CFP Open | CFP "
  },
  {
    "path": "Past Events/2025.md",
    "chars": 2319,
    "preview": "# Australian Developer Events\n\n## Event Archive\n\n### 2025\n\n\n| Event Name | State | Date From | Date To | CFP Open | CFP "
  },
  {
    "path": "Past Events/OTHER.md",
    "chars": 534,
    "preview": "\n# Australian Developer Events\n\n## Other Developer Conference Lists\n\n* [2016 Global Web Development Conferences](https:/"
  },
  {
    "path": "README.md",
    "chars": 1851,
    "preview": "# Australian Developer Events\n\nWe've collated a list of all the events in Australia that might be of interest to softwar"
  },
  {
    "path": "website/.gitignore",
    "chars": 318,
    "preview": "# dependencies\nnode_modules/\n\n# build output\ndist/\n\n# astro generated files\n.astro/\n\n# logs\nnpm-debug.log*\nyarn-debug.lo"
  },
  {
    "path": "website/astro.config.mjs",
    "chars": 224,
    "preview": "import { defineConfig } from 'astro/config';\nimport preact from '@astrojs/preact';\n\nexport default defineConfig({\n  site"
  },
  {
    "path": "website/package.json",
    "chars": 527,
    "preview": "{\n  \"name\": \"devevents-website\",\n  \"type\": \"module\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"dev\": \"astro dev\",\n    \"b"
  },
  {
    "path": "website/public/robots.txt",
    "chars": 72,
    "preview": "User-agent: *\nAllow: /\n\nSitemap: https://devevents.io/sitemap-index.xml\n"
  },
  {
    "path": "website/src/components/EventCard.astro",
    "chars": 1702,
    "preview": "---\nimport type { Event } from '@data/types';\nimport { formatDate } from '@data/parse-date';\n\ninterface Props {\n  event:"
  },
  {
    "path": "website/src/components/EventTable.astro",
    "chars": 1483,
    "preview": "---\nimport type { Event } from '@data/types';\nimport { formatDate } from '@data/parse-date';\n\ninterface Props {\n  events"
  },
  {
    "path": "website/src/components/FilterIsland.tsx",
    "chars": 10290,
    "preview": "import { useState } from 'preact/hooks';\nimport type { Event, StateCode } from '@data/types';\n\ninterface Props {\n  event"
  },
  {
    "path": "website/src/components/Footer.astro",
    "chars": 652,
    "preview": "---\nconst year = new Date().getFullYear();\n---\n\n<footer class=\"footer\">\n  <div class=\"container footer__inner\">\n    <spa"
  },
  {
    "path": "website/src/components/Nav.astro",
    "chars": 1138,
    "preview": "---\ninterface Props {\n  currentPath: string;\n}\n\nconst { currentPath } = Astro.props;\n\nconst links = [\n  { href: '/events"
  },
  {
    "path": "website/src/components/UpcomingEventsIsland.tsx",
    "chars": 2506,
    "preview": "import type { Event } from '@data/types';\n\ninterface Props {\n  events: Event[];\n}\n\nfunction toDate(val: unknown): Date |"
  },
  {
    "path": "website/src/data/normalize-state.ts",
    "chars": 734,
    "preview": "import type { StateCode } from './types.js';\n\nconst STATE_MAP: Record<string, StateCode> = {\n  // Codes\n  act: 'ACT', ns"
  },
  {
    "path": "website/src/data/parse-date.ts",
    "chars": 1143,
    "preview": "const MONTH_MAP: Record<string, number> = {\n  jan: 0, feb: 1, mar: 2, apr: 3, may: 4, jun: 5,\n  jul: 6, aug: 7, sep: 8, "
  },
  {
    "path": "website/src/data/parse-events.ts",
    "chars": 4085,
    "preview": "import { readFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { unified } from 'unified';\nimport r"
  },
  {
    "path": "website/src/data/parse-meetups.ts",
    "chars": 3926,
    "preview": "import { readFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { unified } from 'unified';\nimport r"
  },
  {
    "path": "website/src/data/types.ts",
    "chars": 645,
    "preview": "export type StateCode =\n  | 'ACT' | 'NSW' | 'NT' | 'QLD' | 'SA' | 'TAS' | 'VIC' | 'WA'\n  | 'ALL' | 'OTH' | 'VIRTUAL';\n\ne"
  },
  {
    "path": "website/src/env.d.ts",
    "chars": 84,
    "preview": "/// <reference types=\"astro/client\" />\n/// <reference path=\"../.astro/types.d.ts\" />"
  },
  {
    "path": "website/src/layouts/Base.astro",
    "chars": 1513,
    "preview": "---\nimport Nav from '@components/Nav.astro';\nimport Footer from '@components/Footer.astro';\nimport '../styles/global.css"
  },
  {
    "path": "website/src/pages/404.astro",
    "chars": 1008,
    "preview": "---\nimport Base from '@layouts/Base.astro';\n---\n\n<Base title=\"Page Not Found\">\n  <div class=\"container\">\n    <div style="
  },
  {
    "path": "website/src/pages/about.astro",
    "chars": 2220,
    "preview": "---\nimport Base from '@layouts/Base.astro';\n---\n\n<Base\n  title=\"About\"\n  description=\"About DevEvents — a community-main"
  },
  {
    "path": "website/src/pages/events/index.astro",
    "chars": 1958,
    "preview": "---\nimport Base from '@layouts/Base.astro';\nimport FilterIsland from '@components/FilterIsland';\nimport { parseUpcomingE"
  },
  {
    "path": "website/src/pages/events/past/[year].astro",
    "chars": 2660,
    "preview": "---\nimport Base from '@layouts/Base.astro';\nimport EventTable from '@components/EventTable.astro';\nimport { getAvailable"
  },
  {
    "path": "website/src/pages/events/past/index.astro",
    "chars": 834,
    "preview": "---\nimport Base from '@layouts/Base.astro';\nimport { getAvailableYears } from '@data/parse-events';\n\nconst years = getAv"
  },
  {
    "path": "website/src/pages/index.astro",
    "chars": 4290,
    "preview": "---\nimport Base from '@layouts/Base.astro';\nimport UpcomingEventsIsland from '@components/UpcomingEventsIsland';\nimport "
  },
  {
    "path": "website/src/pages/meetups/[state].astro",
    "chars": 4508,
    "preview": "---\nimport Base from '@layouts/Base.astro';\nimport { getAvailableStates, parseMeetupsByState } from '@data/parse-meetups"
  },
  {
    "path": "website/src/pages/meetups/index.astro",
    "chars": 1878,
    "preview": "---\nimport Base from '@layouts/Base.astro';\nimport { getAvailableStates, parseMeetupsByState } from '@data/parse-meetups"
  },
  {
    "path": "website/src/styles/global.css",
    "chars": 10035,
    "preview": ":root {\n  --color-bg: #fafafa;\n  --color-surface: #ffffff;\n  --color-border: #e5e7eb;\n  --color-text: #111827;\n  --color"
  },
  {
    "path": "website/tsconfig.json",
    "chars": 284,
    "preview": "{\n  \"extends\": \"astro/tsconfigs/strict\",\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"preact\","
  }
]

About this extraction

This page contains the full source code of the Readify/DevEvents GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 51 files (108.4 KB), approximately 33.4k tokens, and a symbol index with 36 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!