[
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Build and Deploy\n\non:\n  push:\n    branches: [main]\n    paths:\n      - 'README.md'\n      - 'Past Events/**'\n      - 'Meetups/**'\n      - 'website/**'\n  workflow_dispatch:\n\npermissions:\n  contents: read\n\njobs:\n  build-and-deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n\n      - uses: actions/setup-node@v6.3.0\n        with:\n          node-version: '22'\n          cache: 'npm'\n          cache-dependency-path: website/package-lock.json\n\n      - name: Install dependencies\n        run: npm ci\n        working-directory: website\n\n      - name: Build site\n        run: npm run build\n        working-directory: website\n\n      - name: Deploy to Cloudflare Pages\n        uses: cloudflare/wrangler-action@v3.1.0\n        with:\n          wranglerVersion: '3.90.0'\n          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}\n          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}\n          command: pages deploy dist --project-name=devevents --branch=main\n          workingDirectory: website\n"
  },
  {
    "path": ".github/workflows/pr-preview.yml",
    "content": "name: PR Build Check\n\non:\n  pull_request:\n    branches: [main]\n    paths:\n      - 'README.md'\n      - 'Past Events/**'\n      - 'Meetups/**'\n      - 'website/**'\n\njobs:\n  build-check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6.0.2\n\n      - uses: actions/setup-node@v6.3.0\n        with:\n          node-version: '25'\n          cache: 'npm'\n          cache-dependency-path: website/package-lock.json\n\n      - name: Install dependencies\n        run: npm ci\n        working-directory: website\n\n      - name: Build site\n        run: npm run build\n        working-directory: website\n"
  },
  {
    "path": "CLAUDE.md",
    "content": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## What This Is\n\nA 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/`).\n\n## Website Development\n\n```bash\ncd website\nnpm install\nnpm run dev      # dev server at localhost:4321\nnpm run build    # build to website/dist/\nnpm run preview  # preview the build\n```\n\nThe 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.\n\n## Structure\n\n- `README.md` — upcoming events table (the primary file for new event additions)\n- `Past Events/<year>.md` — events archived by year\n- `Meetups/<state>.md` — recurring meetups by Australian state (ACT, NSW, NT, QLD, SA, TAS, VIC, WA)\n- `CONTRIBUTING.md` — contribution rules\n- `FAQ.md` — project FAQ\n\n## Data Format Rules\n\nWhen editing the events table, follow these conventions exactly (required for future parsing):\n\n- Dates: `dd-Mmm-yyyy` format (e.g., `01-Jan-2026`). Use `TBC` if unknown.\n- States: `VIC`, `NSW`, `ACT`, `WA`, `SA`, `NT`, `QLD`, `ALL`, `TAS`, `OTH`\n- Events go in **chronological order** within a section\n- No extra spaces or formatting characters in table cells\n- Only Australian-based events of interest to the software development community\n- Links must be verified before adding\n\n## Upcoming vs Past Events\n\n- New events belong in the `README.md` upcoming table\n- When a year ends, its events move to `Past Events/<year>.md` (see the archive commit `fb51c7e` for the pattern)\n- Only active meetup pages are listed; add a link to `README.md` when adding a new state meetup file\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "\n# Australian Developer Events\n\n## Contributing\n\nYou want to help? Great!! Here's what you need to know.\n\n### How to contribute\n\nContributing 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.)\n\n### To submit a change using GitHub markdown editor, do the following\n\n1. Browse to [README.md](README.md), the actual file behind it all\n1. Click the [_Edit this file_ pencil icon](README.md)\n1. Make your changes, respecting the rules below please\n1. Use the _Preview Changes_ tab at the top to make sure everything still looks right\n1. Scroll to the _Propose file change_ section at the bottom\n1. Supply a basic description of your change in the first field (`Add Super Awesome Conf 2019`)\n1. Hit the _Propose file change_ button\n1. After navigating to the next page, hit the _Create pull request_ button\n\n### For content\n\n- We are only interested in Australian based events, and no earlier than 2016\n- The event should be of interest to the dev community (specialist and related topics are fine e.g. AI and agile)\n- Please add events in chronological order within the relevant year\n- Check any links you are adding are correct\n- 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.\n- Ensure no additional spaces or formatting characters are present (this will make parsing easier later)\n- Ensure states are specified as VIC, NSW, ACT, WA, SA, NT, QLD, ALL, TAS, OTH (Other)\n\n### For Meetups\n\n- Currently only active meetup pages are listed on the main page\n- If you are adding meetups to a new state page, ensure you add the page link to the main events page\n"
  },
  {
    "path": "FAQ.md",
    "content": "\n# Australian Developer Events\n\n## Frequently Asked Questions - FAQ\n\n### My event is not listed/wrongly classified/there is a mistake\n\nNo 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.\n\nIf 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.\n\n### What type of events will get listed\n\nWe're looking for events that match the following basic criteria:\n\n- _It's an Australian based event, run anytime from 2016 onwards_\n- _It's of interest to the software development community_\n\nIt 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!\n\n### This should be done using RSS/ATOM/Microformat/JSON/Blockchain etc\n\nThis 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.\n\n### I am not technical - how can I contribute to this\n\nPlease [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.\n\n### What is the license for this project\n\nThis repository is licensed under [**Creative Commons - CC0**](LICENSE.md).\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# Creative Commons\n\n## CC0 1.0 Universal\n\nCREATIVE 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.\n\n### Statement of Purpose\n\nThe 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\").\n\nCertain 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.\n\nFor 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.\n\n1. __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:\n\n    i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;\n\n    ii. moral rights retained by the original author(s) and/or performer(s);\n\n    iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;\n\n    iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;\n\n    v. rights protecting the extraction, dissemination, use and reuse of data in a Work;\n\n    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\n\n    vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.\n\n2. __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.\n\n3. __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.\n\n4. __Limitations and Disclaimers.__\n\n    a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.\n\n    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.\n\n    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.\n\n    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.\n"
  },
  {
    "path": "Meetups/ACT.md",
    "content": "\n# Australian Developer Events\n\n## Australian Capital Territory Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---------- | -------- | ---- | --------- | ---- |\n|  |  |  |  |  |\n"
  },
  {
    "path": "Meetups/NSW.md",
    "content": "\n# Australian Developer Events\n\n## New South Wales Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---------- | -------- | ---- | --------- | ---- |\n|  |  |  |  |  |\n"
  },
  {
    "path": "Meetups/NT.md",
    "content": "\n# Australian Developer Events\n\n## Northern Territory Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---------- | -------- | ---- | --------- | ---- |\n|  |  |  |  |  |\n"
  },
  {
    "path": "Meetups/QLD.md",
    "content": "\n# Australian Developer Events\n\n## Queensland Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---------- | -------- | ---- | --------- | ---- |\n| [Microservices Matters](https://skillsmatter.com/groups/11075-microservices-matters) | Virtual | Skills Matter | Monthly | microservices |\n"
  },
  {
    "path": "Meetups/SA.md",
    "content": "\n# Australian Developer Events\n\n## South Australian Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---------- | -------- | ---- | --------- | ---- |\n| [Adelaide .NET User Group](https://www.adnug.net/) | UniSA City West Campus / Virtual | David Gardiner | Monthly  | .NET |\n| [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 |\n| [Front-End Developers Adelaide](https://www.meetup.com/en-AU/Front-End-Developers-Adelaide-FEDA/) | Belgian Beer Cafe | - | Bimonthly | Front end, Web |\n| [Women in Technology SA](https://www.eventbrite.com.au/o/women-in-technology-sa-30256736884) | Whitmore Hotel / Virtual | - | Monthly | |\n| [Python Adelaide](https://www.meetup.com/pythonadelaide/) | UnA North Terrace Campus / Virtual | - | Monthly | Python|\n| [Junior Developers Adelaide](https://www.meetup.com/junior-developers-adelaide/)| Belgian Beer Cafe | - | Monthly | Junior |\n"
  },
  {
    "path": "Meetups/TAS.md",
    "content": "\n# Australian Developer Events\n\n## Tasmanian Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---------- | -------- | ---- | --------- | ---- |\n|  |  |  |  |  |\n"
  },
  {
    "path": "Meetups/VIC.md",
    "content": "\n# Australian Developer Events\n\n# Victorian Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---------- | -------- | ---- | --------- | ---- |\n| [Angular Melbourne](https://www.meetup.com/Angular-Melbourne/) | 6/180 Flinders St, Melbourne | Netwealth | Quarterly | Angular |\n| [AWS Programming and Tools](https://www.meetup.com/Melbourne-AWS-Programming-and-Tools-Meetup/) | Various | Various | Monthly | AWS |\n| [DDD By Night](https://www.meetup.com/DDD-Melbourne-By-Night/) | 452 Flinders Street, Melbourne | Mantel Group | Bi-Monthly | Various |\n| [GDG Melbourne](https://www.meetup.com/gdg-melbourne/) | 452 Flinders Street, Melbourne | Mantel Group | Monthly | GCP |\n| [Golang Melbourne](https://www.meetup.com/golang-mel/) | 452 Flinders Street, Melbourne | Mantel Group | Monthly | Golang |\n| [Her Tech Circle](https://www.hertechcircle.org/events) | Various | Various | Monthly | Women in Tech |\n| [IRL by Amber](https://lu.ma/SparksXGenesis?k=c) | Various | Various | Monthly | \"Australia's top minds & startups\" |\n| [Machine Learning & AI](https://www.meetup.com/Machine-Learning-AI-Meetup/) | 912 Collins Street, Melbourne | | Monthly | Machine Learning & AI |\n| [MelbJS](http://melbjs.com/) | Level 2/29 Stewart Street, Richmond | Culture Amp | | JavaScript |\n| [Melbourne MLOps Community](https://www.meetup.com/melbourne-mlops-community1/) | 452 Flinders St, Melbourne | Mantel Group | Bi-Monthly | MLOps |\n| [React Melbourne](https://www.meetup.com/React-Melbourne) | 139 Gladstone St, South Melbourne | Kogan | Quarterly | React |\n| [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 |\n| [Tech Leading Ladies](https://www.meetup.com/Tech-Leading-Ladies/) | 550 Bourke St, Melbourne | Zendesk | Monthly | Women in Tech Leadership |\n| [VIC.NET](https://www.meetup.com/VIC-NET-Meetup/) | 5/4 Freshwater Place, Southbank | Microsoft | Bi-Monthly | .NET |\n| [Women Coders](https://www.meetup.com/women-coders/) | Various | Various | Monthly | Technical Women |\n| [Women in Cloud](https://www.meetup.com/women-in-cloud-meetup-group/) | Various | Various | Monthly | Women in Cloud Computing |\n\n## Stale Meetups\n\nUnfortunately, 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.\n\n| Event Name |\n| ---------- |\n| [ALT.NET](https://www.meetup.com/Melbourne-ALT-NET/) |\n| [Junior Developers](https://www.meetup.com/Junior-Developers-Melbourne/) |\n| [The Web Meetup](https://www.meetup.com/the-web) |\n"
  },
  {
    "path": "Meetups/WA.md",
    "content": "\n# Australian Developer Events\n\n## Western Australian Meetups\n\n| Event Name | Location | Host | Frequency | Tags |\n| ---------- | -------- | ---- | --------- | ---- |\n|  |  |  |  |  |\n"
  },
  {
    "path": "Past Events/2016.md",
    "content": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2016\n\n| Event Name | State | Date From | Date To | Tags |\n| ---------- | ----- | --------- | ------- | ---- |\n| [Unite](https://unite.unity.com/) | VIC | 23-Oct-2016 | 31-Oct-2016 | Unity |\n| [MixInConf](http://mixinconf.com/) | WA | 28-Oct-2016 | 28-Oct-2016 | Design/Web |\n"
  },
  {
    "path": "Past Events/2017.md",
    "content": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2017\n\n| Event Name | State | Date From | Date To | Tags |\n| ---------- | ----- | --------- | ------- | ---- |\n| [SQL Saturday](http://sqlsaturday.com/) | NSW | 18-Feb-2017 | 18-Feb-2017 | SQL |\n| [Ignite Australia](https://msftignite.com.au/) | QLD | 13-Feb-2017 | 14-Feb-2017 | Microsoft Technologies |\n| [CrikeyCon](https://www.crikeycon.com/) | QLD | 20-Feb-2017 | 20-Feb-2017 | Security |\n| [APIDays](http://au.apidays.io/) | VIC | 01-Mar-2017 | 01-Mar-2017 | API |\n| [Australian Cyber Security](https://acsc2017.com.au/) | ACT | 14-Mar-2017 | 16-Mar-2017 | Security |\n| [Edge of the Web](http://www.eotw.com.au/) | WA | 26-Mar-2017 | 27-Mar-2017 | Web |\n| [WWW 2017](http://www2017.com.au/) | WA | 03-Apr-2017 | 07-Apr-2017 | Web |\n| [CampJS](http://viii.campjs.com/) | VIC | 08-Apr-2017 | 08-Jul-2017 | JS/Web |\n| [YoW! Brisbane](http://brisbane.yowconference.com.au/) | QLD | 12-Apr-2017 | 12-May-2017 | Agile, Lean, Product |\n| [AWS Summit](https://aws.amazon.com/summits/) | NSW | 04-May-2017 | 04-Jun-2017 | AWS |\n| [Australian Testing Days](https://www.australiantestingdays.com/) | VIC | 20-May-2017 | 21-May-2017 | Testing |\n| [AusCERT](https://www.auscert.org.au/events/2017-May-23-16th-annual-auscert-cyber-security-conf) | QLD | 23-May-2017 | 26-May-2017 | Security |\n| [SQL Saturday](http://sqlsaturday.com/) | QLD | 27-May-2017 | 27-May-2017 | SQL |\n| [Global DevOps Bootcamp](http://globaldevopsbootcamp.com/) | Multi | 17-Jun-2017 | 17-Jun-2017 | Devops |\n| [DDD Sydney]( http://2017.dddsydney.com.au/) | NSW | 15-Jul-2017 | 15-Jul-2017 | Various |\n| [Devops days](https://www.devopsdays.org/) | VIC | 16-Jul-2017 | 16-Jul-2017 | Devops |\n| [LAST Conf](https://www.lastconference.com/) | VIC | 29-Jul-2017 | 30-Jul-2017 | Agile, Lean, Product |\n| [DDD Melbourne](https://www.dddmelbourne.com/) | VIC | 12-Aug-2017 | 12-Aug-2017 | Various |\n| [Dev World](http://www.devworld.com.au/) | VIC | 28-Aug-2017 | 30-Aug-2017 | Various |\n| [AWS DevDay Brisbane](https://aws.amazon.com/devday/australia/) | QLD | 05-Sep-2017 | 05-Sep-2017 | AWS |\n| [AWS DevDay Melbourne](https://aws.amazon.com/devday/australia/) | VIC | 07-Sep-2017 | 05-Sep-2017 | AWS |\n| [OWASP AppSec AU Conference](https://www.meetup.com/en-AU/Application-Security-OWASP-Melbourne/events/241082215/) | VIC | 09-Sep-2017 | 09-Sep-2017 | Security |\n| [DDD Perth](https://dddperth.com/) | WA | 16-Sep-2017 | 16-Sep-2017 | Various |\n| [NDC Sydney](https://ndcsydney.com/) | NSW | 17-Sep-2017 | 21-Sep-2017 | Various |\n| [Yow Connected](http://connected.yowconference.com.au/) | VIC | 21-Sep-2017 | 22-Sep-2017 | Mobile |\n| [Aus Scrum](http://scrum.com.au/2017/) | VIC | 21-Sep-2017 | 22-Sep-2017 | Scrum |\n| [YoW! Data](http://data.yowconference.com.au/) | NSW | 22-Sep-2017 | 23-Sep-2017 | Data |\n| [Devops days](https://www.devopsdays.org/) | WA | 14-Oct-2017 | 15-Oct-2017 | Devops |\n| [Agile Encore](http://www.agileencore.com/) | VIC | 21-Oct-2017 | 21-Oct-2017 | Agile |\n| [Ruxcon](https://ruxcon.org.au) | VIC | 21-Oct-2017 | 22-Oct-2017 | Security |\n| [Pax](http://aus.paxsite.com/) | VIC | 27-Oct-2017 | 29-Oct-2017 | Games |\n| [Rails Camp](https://rails.camp/#au_nov_2017) | VIC | 01-Nov-2017 | 02-Nov-2017 | Rails |\n| [HealthHack](https://www.healthhack.com.au/) | Multi | 03-Nov-2017 | 04-Nov-2017 | Hackathon |\n| [Agile tour Sydney](https://www.eventbrite.com.au/e/agile-tour-sydney-2017-tickets-29363059702) | VIC | 06-Nov-2017 | 06-Nov-2017 | Agile |\n| [Open Stack Summit](https://www.openstack.org/summit/) | NSW | 06-Nov-2017 | 08-Nov-2017 | Infrastructure |\n| [Latency Conf](https://www.latencyconf.io/) | WA | 16-Nov-2017 | 16-Nov-2017 | High performance apps |\n| [Puppet Camp](https://puppet.com/community/events/camp/puppet-camp-melbourne-2017) | VIC | 17-Nov-2017 | 17-Nov-2017 | Puppet |\n| [Global Day of Code Retreat](http://coderetreat.org/) | VIC | 18-Nov-2017 | 18-Nov-2017 | Various|\n| [Write the docs](http://www.writethedocs.org/) | VIC | 24-Nov-2017 | 24-Nov-2017 | Technical Documentation |\n| [YoW! Melbourne](http://melbourne.yowconference.com.au/) | VIC | 30-Nov-2017 | 01-Dec-2017 | Agile, Lean, Product |\n| [BuzzConf](https://buzzconf.io/) | VIC | 01-Dec-2017 | 03-Dec-2017 | Future Tech |\n| [DDD Brisbane](http://www.dddbrisbane.com/) | QLD | 02-Dec-2017 | 02-Dec-2017| Various |\n| [YOW! Brisbane](http://brisbane.yowconference.com.au/) | QLD | 04-Dec-2017 | 05-Dec-2017 | Various |\n| [YOW! Sydney](http://sydney.yowconference.com.au/) | NSW | 07-Dec-2017 | 08-Dec-2017 | Various |\n| [TConf](https://tconf.io/) | VIC | 08-Dec-2017 | 08-Dec-2017 | Testing |\n"
  },
  {
    "path": "Past Events/2018.md",
    "content": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2018\n\n| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |\n| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |\n| [Container Camp](https://2018.container.camp/au/) | VIC | 24-May-2018 | 25-May-2018 | | | Containers |\n| [Angular Conf](https://www.angularconf.com.au/) | VIC | 22-Jun-2018 | 22-Jun-2018 | | | Web |\n| [Levels Conf](https://www.levelsconf.com/index.html) | VIC | 07-Jul-2018 | 07-Jul-2018 | | | Web |\n| [DDD Melbourne](https://www.dddmelbourne.com/) | VIC | 15-Sep-2018 | | 27-Apr-2018 | 19-Jun-2018 | All the things |\n| [NDC Sydney](http://www.ndcsydney.com/) | NSW | 17-Sep-2018 | 21-Sep-2018 | | | Various |\n| [YOW! Connected](http://connected.yowconference.com.au/) | VIC | 17-Sep-2018 | 18-Sep-2018 |  |  | IOT |\n| [Business Agility Conference](http://businessagility.yowconference.com.au/) | NSW | 24-Sep-2018 | 25-Sep-2018 | Feb-2018 | 31-May-2018 | Agile |\n| [A11y Camp](https://a11ycamp.org.au/) | VIC | 17-Oct-2018 | 19-Oct-2018 | | | Accessibility |\n| [AISA Australian Cyber Conference](https://cyberconference.com.au) | VIC | 09-Oct-2018 | 11-Oct-2018 | | | Security |\n| [OWASP AppSec Day](https://appsecday.io/) | VIC | 19-Oct-2018 | 19-Oct-2018 | | | Security |\n| [Test Bash Australia](https://www.ministryoftesting.com/2017/11/testbash-australia-throw-another-testbash-barbie/) | NSW | 19-Oct-2018 |  | | | Testing |\n| [MeasureCamp Sydney](http://sydney.measurecamp.org/) | NSW | 20-Oct-2018 | | | | Analytics |\n| [DevFest](https://www.gdgmelbourne.com/devfest-2018/) | VIC | 27-Oct-2018 | 27-Oct-2018 | | | Android |\n| [Web Directions AI](http://www.webdirections.org/ai/) | NSW | 31-Oct-2018 | | | | AI |\n| [Web Directions Culture](http://www.webdirections.org/culture/) | NSW | 31-Oct-2018 |  | | | Culture |\n| [Web Directions Summit](http://www.webdirections.org/wds/) | NSW | 01-Nov-2018 | 02-Nov-2018 | 01-Dec-2017 | May 2018 | Web (front end/design) |\n| [Latency Conf](https://www.latencyconf.io/) | WA | 15-Nov-2018 | 15-Nov-2018 |   |  27-Jul-2018 | High Performance Apps |\n| [TConf 2018](https://www.eventbrite.com.au/e/tconf-2018-melbournes-own-software-testing-conference-tickets-39909638804?aff=es2) | VIC | 23-Nov-2018 | | | | Testing |\n| [GDG Cloud](https://gdgcloud.melbourne/) | VIC | 24-Nov-2018 | 24-Nov-2018 | | | Google Cloud |\n| [YOW! Sydney](http://sydney.yowconference.com.au/) | NSW | 29-Nov-2018 | 30-Nov-2018 |  |  | Various |\n| [DDD Brisbane](http://dddbrisbane.com/) | QLD | 01-Dec-2018 |  | TBC | TBC | All the things |\n| [YOW! Brisbane](http://brisbane.yowconference.com.au/) | QLD | 03-Dec-2018 | 04-Dec-2018 |  |  | Various |\n| [YOW! Melbourne](http://melbourne.yowconference.com.au/) | VIC | 06-Dec-2018 | 07-Dec-2018 |  |  | Various |\n"
  },
  {
    "path": "Past Events/2019.md",
    "content": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2019\n\n| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |\n| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |\n| [DataWorks Summit](https://10times.com/dataworks-summit-melbourne) | VIC | 06-Feb-2019 | 06-Feb-2019 |  |  | Data, AI Machine Learning |\n| [RubyConf](https://www.rubyconf.org.au/2019) | VIC | 07-Feb-2019 | 09-Feb-2019 | 03-Sep-2018 | 15-Oct-2018 | Ruby |\n| [Microservices, Containers & Serverless Day](https://1point21gws.com/microservices/melbourne/) | VIC | 08-Feb-2019 | 08-Feb-2019 |  |  | Microservices |\n| [Microsoft Ignite Tour](https://www.microsoft.com/en-au/ignite-the-tour/sydney) | NSW | 13-Feb-2019 | 14-Feb-2019 |  |  | Cloud Infrastructure |\n| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/163) | WA | 23-Feb-2019 | 23-Feb-2019 |  |  | Tech Talks |\n| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/122) | VIC | 02-Mar-2019 | 02-Mar-2019 |  |  | Tech Talks |\n| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/89) | NSW | 02-Mar-2019 | 02-Mar-2019 |  |  | Tech Talks |\n| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/199) | QLD | 02-Mar-2019 | 02-Mar-2019 |  |  | Tech Talks |\n| [BSides Canberra](https://www.bsidescbr.com.au/) | ACT | 15-Mar-2019 | 16-Mar-2019 | 01-Nov-2018 | 31-Dec-2018 | Security |\n| [DevOps Talks Conference](https://devopstalks.com/au/devops.html/) | VIC | 19-Mar-2019 | 22-Mar-2019 |  |  | DevOps |\n| [CrikeyCon](https://www.crikeycon.com/) | QLD | 06-Apr-2019 | 07-Apr-2019 | 01-Jan-2019 | 06-Feb-2019 | Security |\n| [Web Directions Design](https://www.webdirections.org/design/) | VIC | 11-Apr-2019 | 12-Apr-2019 |  |  | Design, UX |\n| [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 |\n| [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 |\n| 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 |\n| [Pivot Summit](https://www.pivotsummit.com.au/) | VIC | 03-May-2019 | 04-May-2019 |  |  | Various |\n| [YOW! Data](https://data.yowconference.com.au/) | NSW | 06-May-2019 | 07-May-2019 | 01-Jan-2019 | 22-Mar-2019 | Data |\n| [Rails Camp](https://rails.camp/) | WA | 10-May-2019 | 13-May-2019 | | | Ruby |\n| [YOW! Lambda Jam](https://lambdajam.yowconference.com.au/) | VIC | 13-May-2019 | 15-May-2019 | 22-Mar-2019 | 22-Mar-2019 | Functional Programming |\n| [Voxxed Days](https://australia.voxxeddays.com/) | VIC | 13-May-2019 | 14-May-2019 | 24-Oct-2018 | 11-Feb-2019 | Various |\n| [Catalyst Conference](https://catalystaustralia.girlsintech.org/) | VIC | 15-May-2019 | 16-May-2019 | | | Women in Tech |\n| [Voxxed Days](https://australia.voxxeddays.com/) | NSW | 16-May-2019 | 17-May-2019 | 25-Oct-2018 | 11-Feb-2019 | Various |\n| [Australian Test & Tech Automation Conference (ATTAC)](https://attac.tech/) | VIC | 24-May-2019 | 24-May-2019 | | | Testing, QA |\n| [LAST Conference Adelaide](https://www.lastconference.com/adelaide/) | SA | 30-May-2019 | 30-May-2019 | | | Agile, Lean, Systems Thinking |\n| [SQL Saturday Brisbane](https://www.sqlsaturday.com/838/eventhome.aspx) | QLD | 31-May-2019 | 01-Jun-2019 | 01-Jan-2019 | 02-Apr-2019 | Data |\n| [SQL Saturday Melbourne](https://www.sqlsaturday.com/865/eventhome.aspx) | VIC | 14-Jun-2019 | 15-Jun-2019 | 01-Feb-2019 | 16-Apr-2019 | Data |\n| [OzSecCon](https://www.ozseccon.com/) | VIC | 14-Jun-2019 | 16-Jun-2019 |  | 31-Mar-2019 | Security |\n| [Global DevOps Bootcamp](https://www.eventbrite.com/e/global-devops-bootcamp-telstrareadify-tickets-57006764768) | NSW | 15-Jun-2019 | 15-Jun-2019 | | | DevOps, Azure |\n| [Web Directions Code](https://www.webdirections.org/code/) | VIC | 20-Jun-2019 | 21-Jun-2019 |  |  | JavaScript, Front End, Design |\n| [AgileAus 2019](http://agileaustralia.com.au/2019/) | NSW | 25-Jun-2019 | 26-Jun-2019 |  |  | Agile |\n| [XConf](https://www.thoughtworks.com/xconf-au-2019) | NSW | 23-Jul-2019 | 23-Jul-2019 |  |  | Thoughtworks |\n| [XConf](https://www.thoughtworks.com/xconf-au-2019) | VIC | 25-Jul-2019 | 25-Jul-2019 |  |  | Thoughtworks |\n| [Container Camp](https://2019.container.camp/au/) | NSW | 25-Jul-2019 | 26-Jul-2019 |  |  | Container, Kubernetes, Docker |\n| [LAST Conference Melbourne](https://www.lastconference.com/melbourne/) | VIC | 30-Jul-2019 | 30-Jul-2019 | | | Agile, Lean, Systems Thinking |\n| [Web Directions Product](https://www.webdirections.org/product/) | VIC | 1-Aug-2019 | 2-Aug-2019 |  |  | Product, Design |\n| [PyCon AU](https://2019.pycon-au.org/) | NSW | 2-Aug-2019 | 6-Aug-2019 | 3-Apr-2019 | 5-May-2019 | Python |\n| [SQL Saturday Sydney](https://www.sqlsaturday.com/875/eventhome.aspx) | NSW | 03-Aug-2019 | 03-Aug-2019 |  |  | Data |\n| [DDD Perth](https://dddperth.com/) | WA | 3-Aug-2019 | 3-Aug-2019 | 30-Apr-2019 | 2-Jun-2019 | Various |\n| [DDD Melbourne](https://dddmelbourne.com/) | VIC | 10-Aug-2019 | 10-Aug-2019 | 13-Apr-2019 | 15-Jun-2019 | Various |\n| [Australia Summit for Microsoft Business Applications](https://www.australiasummit.com/home) | VIC | 21-Aug-2019 | 23-Aug-2019 | | | Data, Business Applications, Power Platform |\n| [ServerlessDays Sydney](https://sydney.serverlessdays.io/) | NSW | 27-Aug-2019 | 27-Aug-2019 | 25-Mar-2019 | 20-May-2019 | Serverless |\n| [ServerlessDays Melbourne](https://www.serverlessdays.me/) | VIC | 29-Aug-2019 | 29-Aug-2019 | 01-May-2019 | 09-Jun-2019 | Serverless |\n| [LAST Conference Sydney](https://www.lastconference.com/sydney/) | NSW | 29-Aug-2019 | 29-Aug-2019 | | | Agile, Lean, Systems Thinking |\n| [YOW! Perth](https://yowconference.com/) | WA | 4-Sep-2019 | 5-Sep-2019 |  |  | Various |\n| [Global AI Night](https://www.meetup.com/Melbourne-Azure-Nights/events/264397932/) | VIC | 5-Sep-2019 | 5-Sep-2019 | | | ML, A, Bots|\n| [ComponentsConf](https://www.componentsconf.com.au/) | VIC | 6-Sep-2019 | 6-Sep-2019 | 01-Dec-2018 | 06-Apr-2019 | Front End |\n| [SQL Saturday Perth](https://www.sqlsaturday.com/894/eventhome.aspx) | WA | 07-Sep-2019 | 07-Sep-2019 |  |  | Data |\n| [YOW! Business Agility Conference](https://businessagility.yowconference.com.au/) | VIC | 16-Sep-2019 | 17-Sep-2019 | 01-Feb-2019 | 30-Apr-2019 | Agile |\n| [APIDays](https://www.apidays.co) | VIC | 00-Sep-2019 | 00-Sep-2019 | 00-Jan-2019 | 00-Aug-2019 | Various |\n| [DDD Sydney](https://next.dddsydney.com.au) | NSW | 21-Sep-2019 | 21-Sep-2019 | 10-Jun-2019 | 14-Jul-2019 | Various |\n| [LAST Conference Brisbane](https://www.lastconference.com/brisbane/) | QLD | 04-Oct-2019 | 04-Oct-2019 | | | Agile, Lean, Systems Thinking |\n| [Devopsdays Sydney 2019](https://devopsdays.org/events/2019-sydney/welcome/) | NSW | 10-Oct-2019 | 11-Oct-2019 | 03-Jun-2019  | 05-Aug-2019 | Various |\n| [NDC Sydney 2019](https://ndcsydney.com/) | NSW | 14-Oct-2019 | 18-Oct-2019 | 10-Feb-2019  | 15-Jun-2019 | Various |\n| [JetBrains Night Sydney 2019](https://info.jetbrains.com/jetbrains-night-sydney-2019.html) | NSW | 15-Oct-2019 | 15-Oct-2019 | | | Various |\n| [LAST Conference ACT](https://www.lastconference.com/canberra/) | ACT | 16-Oct-2019 | 16-Oct-2019 | | | Agile, Lean, Systems Thinking |\n| [JetBrains Meetup Melbourne 2019](https://info.jetbrains.com/jetbrains-meetup-melbourne-2019.html) | VIC | 21-Oct-2019 | 21-Oct-2019 | | | Various |\n| [JetBrains Night Brisbane 2019](https://info.jetbrains.com/jetbrains-night-brisbane-2019.html) | QLD | 22-Oct-2019 | 22-Oct-2019 | | | Various |\n| [GopherCon AU](https://gophercon.com.au/) | NSW | 30-Oct-2019 | 1-Nov-2019 | 01-May-2019 | 30-Jun-2019 | Golang |\n| [Web Directions Summit](https://www.webdirections.org/wds/) | NSW | 31-Oct-2019 | 1-Nov-2019 |  |  | Front End, JavaScript, Product, Design |\n| [Latency Conf](https://www.latencyconf.io/) | WA | 14-Nov-2019 | 15-Nov-2019 |  |  | Cloud Native |\n| [GDG DevFest Melbourne](https://www.gdgmelbourne.com/devfest) | VIC | 09-Nov-2019 | 09-Nov-2019 |  |  | Google Tech |\n| [DDD Adelaide](https://dddadelaide.com) | SA | 23-Nov-2019 | 23-Nov-2019 | 1-Aug-2019 | 4-Sep-2019 | Various |\n| [TConf](https://tconf.io/) | VIC | 29-Nov-2019 | 29-Nov-2019 | 01-May-2019 | 30-Jun-2019  | Testing, QA |\n| [YOW! Sydney](http://sydney.yowconference.com.au/) | NSW | 05-Dec-2019 | 06-Dec-2019 |  |  | Various |\n| [Microservices, Containers & Serverless Day](https://1point21gws.com/microservices/melbourne/) | VIC | 05-Dec-2019 | 05-Dec-2019 |  |  | Microservices |\n| [YOW! Brisbane](http://brisbane.yowconference.com.au/) | QLD | 09-Dec-2019 | 10-Dec-2019 |  |  | Various |\n| [YOW! Melbourne](http://melbourne.yowconference.com.au/) | VIC | 12-Dec-2019 | 13-Dec-2019 |  |  | Various |\n| [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 |"
  },
  {
    "path": "Past Events/2020.md",
    "content": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2020\n\n| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |\n| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |\n| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/223) | WA | 18-Jan-2020 | 18-Jan-2020 ||| Tech Talks |\n| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/236) | VIC | 18-Jan-2020 | 18-Jan-2020 ||| Tech Talks |\n| [Global Diversity CFP Day (Sydney)](https://www.globaldiversitycfpday.com/events/215) | NSW | 18-Jan-2020 | 18-Jan-2020 ||| Tech Talks |\n| [Global Diversity CFP Day (Newcastle)](https://www.globaldiversitycfpday.com/events/245) | NSW | 18-Jan-2020 | 18-Jan-2020 ||| Tech Talks |\n| [Global Diversity CFP Day](https://www.globaldiversitycfpday.com/events/240) | QLD | 18-Jan-2020 | 18-Jan-2020 ||| Tech Talks |\n| [Australian Accessibility Conference](https://ozewai.org/conference/) | WA | 11-Feb-2020 | 13-Feb-2020 ||| Accessibility | \n| [Microsoft Ignite Tour](https://www.microsoft.com/en-au/ignite-the-tour/sydney) | NSW | 13-Feb-2020 | 14-Feb-2020 ||| Cloud Infrastructure |\n| [RubyConf](https://www.rubyconf.org.au/2020) | VIC | 20-Feb-2020 | 21-Feb-2020 || 8-Nov-2019 | Ruby |\n| [ReactConf AU](https://www.reactconf.com.au) | NSW | 27-Feb-2020 | 28-Feb-2020 || | JavaScript, React |\n| [DevOps Talks Conference](https://devopstalks.com/au/devops.html/) | VIC | 18-Mar-2020 | 20-Mar-2020 || 15-Jan-2020 | DevOps |\n| [YOW! Data](https://data.yowconference.com.au/) | NSW | 29-Apr-2020 | 1-May-2020 || 16-Feb-2020 | Data |\n| [BSides Canberra](https://www.bsidescbr.com.au/) | ACT | 1-May-2020 |2-May-2020 || 15-Jan-2020 | Security |\n| [Pivot Summit](https://www.pivotsummit.com.au/) | VIC | 3-May-2020 | 4-May-2020 ||| Various |\n| [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 |\n| [Web Directions Code Leaders](https://www.webdirections.org/leaders/) | VIC | 03-Jun-2020 | 03-Jun-2020 ||| Leadership |\n| [Web Directions Code](https://www.webdirections.org/code/) | VIC | 04-Jun-2020 | 05-Jun-2020 ||| Web |\n| [AgileAus 2020](http://agileaustralia.com.au/2020/) | NSW | 15-Jun-2020 | 16-Jun-2020 ||| Agile |\n| [Web Directions Product](https://www.webdirections.org/product/) | VIC | 01-Jul-2020 | 02-Jul-2020 ||| Product Design |\n| [Web Directions Design Leaders](https://www.webdirections.org/designleaders/) | VIC | 03-Jul-2020 | 03-Jul-2020 ||| Leadership |\n| [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 |\n| [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 |\n| [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 |\n| [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 |\n| [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 |\n| [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 |\n| [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 |\n"
  },
  {
    "path": "Past Events/2022.md",
    "content": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2022\n\n| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |\n| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |\n| [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|\n| [Rust Forum](https://skillsmatter.com/conferences/13771-rust-forum) | Virtual | 24-May-2022 | 24-May-2022 |  | | rust|\n| [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|\n| [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|\n| [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|\n| [DDD Perth 2022](https://dddperth.com/) | Perth | 10-Sep-2022 | 10-Sept-2022 |  | | various |\n| [API Days](https://www.apidays.global/australia/) | Melbourne | 14-Sep-2022 | 15-Sept-2022 |  | | API, Integration, Networks, Business|\n| [YOW! Perth 2022](https://skillsmatter.com/conferences/13732-yow-perth-2022) | Perth | 19-Sep-2022 | 20-Sept-2022 |  | | various|\n| [YOW! London 2022](https://skillsmatter.com/conferences/13691-yow-london-online) | Virtual | 22-Sep-2022 | 23-Sept-2022 |  | | various|\n| [NDC Sydney 2022](https://ndcsydney.com/) | NSW | 10-Oct-2022 | 14-Oct-2022||| Tech Talks, Workshops |\n| [Testing Talks Conference](https://www.testingtalks.com.au/) | VIC | 20-Oct-2022 | 20-Oct-2022 | Testing || Tech Talks |\n| [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|\n| [Web Directions Code Leaders](https://webdirections.org/leaders) | NSW | 30-Nov-2022 | 30-Nov-2022 | Engineering Leadership|| Tech Talks |\n| [Web Directions Summit](https://webdirections.org/summit) | NSW | 01-Dec-2022 | 02-Dec-2022 | Product, design, FE dev|| Tech Talks |\n| [DDD Brisbane 2022](https://www.dddbrisbane.com/) | QLD | 03-Dec-2022 | 03-Dec-2022 | 05-Sept-2022 | 03-Oct-2022 | Tech Talks |\n| [YOW! Brisbane 2022](https://www.skillsmatter.com/conferences/13735-yow-brisbane-2022) | Brisbane | 5-Dec-2022 | 6-Dec-2022 |  |  | various|\n| [YOW! Melbourne 2022](https://www.skillsmatter.com/conferences/13733-yow-melbourne-2022) | Melbourne | 8-Dec-2022 | 9-Dec-2022 |  |  | various|\n| [Haskell eXchange 2022](https://skillsmatter.com/conferences/13688-haskell-exchange-2022) | Virtual | 8-Dec-2022 | 9-Dec-2022 |  |  | Haskell|\n| [YOW! Sydney 2022](https://www.skillsmatter.com/conferences/13734-yow-sydney-2022) | Sydney | 12-Dec-2022 | 13-Dec-2022 |  |  | various|\n"
  },
  {
    "path": "Past Events/2024.md",
    "content": "\n# Australian Developer Events\n\n## Event Archive\n\n### 2024\n\n| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |\n| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |\n| [YOW! Tech Leaders Summit](https://yowcon.com/melbourne-2024) | Melbourne | 11-Sep-2024 | | | | Tech Leadership |\n| [YOW! Tech Leaders Summit](https://yowcon.com/brisbane-2024) | Brisbane | 12-Sep-2024 | | | | Tech Leadership |\n| [YOW! Tech Leaders Summit](https://yowcon.com/sydney-2024) | Sydney | 13-Sep-2024 | | | | Tech Leadership |\n| [DataEngBytes](https://dataengconf.com.au/) | Sydney | 24-Sep-2024 | | 18-Mar-2024 | 14-Jul-2024 | Data |\n| [DataEngBytes](https://dataengconf.com.au/) | Perth | 27-Sep-2024 | | 18-Mar-2024 | 14-Jul-2024 | Data |\n| [LAST Conference](https://www.lastconference.com/brisbane/) | Brisbane | 27-Sep-2024 | | | | Lean Agile Systems Thinking |\n| [DataEngBytes](https://dataengconf.com.au/) | Melbourne | 1-Oct-2024 | | 18-Mar-2024 | 14-Jul-2024 | Data |\n| [DataEngBytes](https://dataengconf.com.au/) | Auckland | 4-Oct-2024 | | 18-Mar-2024 | 14-Jul-2024 | Data |\n| [GDG Melbourne DevFest](https://gdgmelbourne.com/) | Melbourne | 5-Oct-2024 | | 30-Jul-2024 | 30-Aug-2024 | Google Tech |\n| [APIDays](https://www.apidays.global/australia/) | Melbourne | 16-Oct-2024 | 17-Oct-2024 | NOW | 16-Sep-2024 | APIs |\n| [Testing Talks](https://www.testingtalks.com.au/upcoming-events/testing-talks-conference-2024-melbourne) | Melbourne | 17-Oct-2024 | | Now? | ? | Testing |\n| [LAST Conference](https://www.lastconference.com/sydney/) | Sydney | 18-Oct-2024 | | | | Lean Agile Systems Thinking |\n| [GopherCon](https://gophercon.com.au/) | Sydney | 7-Nov-2024 | 8-Nov-2024 | NOW | 30-Aug-2024 | Golang |\n| [DDD Perth](https://dddperth.com/) | Perth | 16-Nov-2024 | | | 12-Jul-2023 | Various |\n| [DDD Adelaide](https://dddadelaide.com/) | Adelaide | 23-Nov-2024 | | 1-Aug-2024 | 6-Sep-2024 | Various |\n| [Web Directions Developer Summit](https://webdirections.org/dev-summit/) | Sydney | 27-Nov-2024 | 28-Nov-2024 | | | Web Dev |\n| [LAST Conference](https://clubhouse.lastconference.com/lastmel24/) | Melbourne | 29-Nov-2024 | | | | Lean Agile Systems Thinking |\n| [YOW!](https://yowcon.com/melbourne-2024) | Melbourne | 5-Dec-2024 | 6-Dec-2024 | | | Various |\n| [YOW!](https://yowcon.com/brisbane-2024) | Brisbane | 9-Dec-2024 | 10-Dec-2024 | | | Various |\n| [YOW!](https://yowcon.com/sydney-2024) | Sydney | 12-Dec-2024 | 13-Dec-2024 | | | Various |\n"
  },
  {
    "path": "Past Events/2025.md",
    "content": "# Australian Developer Events\n\n## Event Archive\n\n### 2025\n\n\n| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |\n| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |\n| [NDC Sydney](https://tickets.ndcconferences.com/tickets/index/sydney2024) | Sydney | 12-Feb-2025 | 16-Feb-2025 | | | Various |\n| [DDD Melbourne](https://www.dddmelbourne.com/) | Melbourne | 22-Feb-2025 | | | | Various |\n| [NDC Melbourne](https://tickets.ndcconferences.com/tickets/index/melbourne2025) | Melbourne | 28-Apr-2025 | 1-May-2025 | | | Various |\n| [Vogue Codes Summit](https://www.vogue.com.au/vogue-codes) | Sydney | 14-Jun-2025 | | | | Women in Tech |\n| [YOW! Tech Leaders Summit Melbourne](https://yowcon.com/tech-leaders-melbourne-2025) | Melbourne | 19-Jun-2025 | | | | Tech Leadership |\n| [YOW! Tech Leaders Summit Sydney](https://yowcon.com/tech-leaders-sydney-2025) | Sydney | 18-Jun-2025 | | | | Tech Leadership |\n| [YOW! Tech Leaders Summit Brisbane](https://yowcon.com/tech-leaders-brisbane-2025) | Brisbane | 17-Jun-2025 | | | | Tech Leadership |\n| [DataEngBytes Melbourne](https://dataengbytes.com/melbourne) | Melbourne | 24-Jul-2025 | 25-Jul-2025 | | | Data Eng & MLOps |\n| [DataEngBytes Sydney](https://dataengbytes.com/sydney) | Sydney | 29-Jul-2025 | 30-Jul-2025 | | | Data Eng & MLOps |\n| [PyConAU](https://2025.pycon.org.au/) | Melbourne | 12-Sep-2025 | 16-Sep-2025 | | | Python |\n| [Testing Talks](https://www.testingtalks.com.au/) | Melbourne | 9-Oct-2025 | | | | Testing |\n| [apidays](https://www.apidays.global/events/australia) | Melbourne | 29-Oct-2025 | 30-Oct-2025 | | | APIs |\n| [Web Directions Developer Summit](https://webdirections.org/dev-summit/index.php) | Sydney | 19-Nov-2025 | 20-Nov-2025 | | | Web |\n| [LAST Conference](https://clubhouse.lastconference.com/lastmel25/) | Melbourne | 21-Nov-2025 | | | | Various |\n| [DevOpsDays](https://devopsdays.org/events/2025-wollongong/welcome/) | Wollongong | 27-Nov-2025 | 28-Nov-2025 | | | Various |\n| [YOW! Melbourne](https://yowcon.com/melbourne-2025) | Melbourne | 4-Dec-2025 | 5-Dec-2025 | | | Various |\n| [YOW! Brisbane](https://yowcon.com/brisbane-2025) | Brisbane | 8-Dec-2025 | 9-Dec-2025 | | | Various |\n| [YOW! Sydney](https://yowcon.com/sydney-2025) | Sydney | 11-Dec-2025 | 12-Dec-2025 | | | Various |\n\n\n"
  },
  {
    "path": "Past Events/OTHER.md",
    "content": "\n# Australian Developer Events\n\n## Other Developer Conference Lists\n\n* [2016 Global Web Development Conferences](https://github.com/ryanburgess/2016-conferences)\n* [2017 Global Web Development Conferences](https://github.com/ryanburgess/2017-conferences)\n* [2018 Global Web Development Conferences](https://github.com/ryanburgess/2018-conferences)\n* [2019 Global Web Development Conferences](https://github.com/ryanburgess/2019-conferences)\n* [2020 Global Web Development Conferences](https://github.com/ryanburgess/2019-conferences)\n"
  },
  {
    "path": "README.md",
    "content": "# Australian Developer Events\n\nWe'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.\n\n## Upcoming Events\n\n| Event Name | State | Date From | Date To | CFP Open | CFP Close | Tags |\n| ---------- | ----- | --------- | ------- | -------- | --------- | ---- |\n| [DDD Melbourne 2026](https://www.dddmelbourne.com/) | Melbourne | 21-Feb-2026 | | | 30-Sep-2025 | Various |\n| [Programmable Melbourne 2026](https://www.programmable.tech/) | Melbourne | 17-Mar-2026 | | | 2-Dec-2025 | Various |\n| [Programmable Sydney 2026](https://www.programmable.tech/) | Sydney | 19-Mar-2026 | | | 2-Dec-2025 | Various |\n| [NDC Sydney 2026](https://ndcsydney.com/) | Sydney | 22-Apr-2026 | 24-Apr-2026 | | 6-Dec-2025 | Various |\n| [AI Engineer Melbourne](https://webdirections.org/ai-engineer/index.php) | Melbourne | 3-Jun-2026 | 4-Jun-2026 | | 28-Feb-2026 | Various |\n\n## Prior Years\n\n- [2025 Events](Past%20Events/2025.md)\n- [2024 Events](Past%20Events/2024.md)\n- [2022 Events](Past%20Events/2022.md)\n- [2020 Events](Past%20Events/2020.md)\n- [2019 Events](Past%20Events/2019.md)\n- [2018 Events](Past%20Events/2018.md)\n- [2017 Events](Past%20Events/2017.md)\n- [2016 Events](Past%20Events/2016.md)\n\n## Meetups\n\n- [ACT](Meetups/ACT.md)\n- [NSW](Meetups/NSW.md)\n- [NT](Meetups/NT.md)\n- [QLD](Meetups/QLD.md)\n- [SA](Meetups/SA.md)\n- [TAS](Meetups/TAS.md)\n- [VIC](Meetups/VIC.md)\n- [WA](Meetups/WA.md)\n\n## More Information\n\n### FAQ\n\nGot a question? [The FAQ](FAQ.md) probably has the answer you need.\n\n### Other Conferences\n\nThere are lists of conferences that others are compiling too. You can [find them here](Past%20Events/OTHER.md).\n\n### Can I contribute, or add an event?\n\nOf course! We'd love you to! See the [contribution guide](CONTRIBUTING.md) for how.\n"
  },
  {
    "path": "website/.gitignore",
    "content": "# dependencies\nnode_modules/\n\n# build output\ndist/\n\n# astro generated files\n.astro/\n\n# logs\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\n\n# env files\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# OS/editor files\n.DS_Store\nThumbs.db\n.vscode/*\n!.vscode/extensions.json"
  },
  {
    "path": "website/astro.config.mjs",
    "content": "import { defineConfig } from 'astro/config';\nimport preact from '@astrojs/preact';\n\nexport default defineConfig({\n  site: 'https://devevents.io',\n  integrations: [\n    preact({ compat: false }),\n  ],\n  output: 'static',\n});\n"
  },
  {
    "path": "website/package.json",
    "content": "{\n  \"name\": \"devevents-website\",\n  \"type\": \"module\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"dev\": \"astro dev\",\n    \"build\": \"astro build\",\n    \"preview\": \"astro preview\"\n  },\n  \"dependencies\": {\n    \"@astrojs/preact\": \"^5.0.2\",\n    \"@astrojs/sitemap\": \"^3.7.1\",\n    \"astro\": \"^6.0.8\",\n    \"mdast-util-to-string\": \"^4.0.0\",\n    \"preact\": \"^10.24.0\",\n    \"remark-gfm\": \"^4.0.0\",\n    \"remark-parse\": \"^11.0.0\",\n    \"unified\": \"^11.0.5\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^25.0.0\",\n    \"typescript\": \"^5.5.0\"\n  }\n}\n"
  },
  {
    "path": "website/public/robots.txt",
    "content": "User-agent: *\nAllow: /\n\nSitemap: https://devevents.io/sitemap-index.xml\n"
  },
  {
    "path": "website/src/components/EventCard.astro",
    "content": "---\nimport type { Event } from '@data/types';\nimport { formatDate } from '@data/parse-date';\n\ninterface Props {\n  event: Event;\n}\n\nconst { event } = Astro.props;\n\nconst now = new Date();\nconst cfpOpen = event.cfpOpen && event.cfpClose\n  ? now >= event.cfpOpen && now <= event.cfpClose\n  : null;\nconst cfpClosed = event.cfpClose && now > event.cfpClose;\n\nconst dateStr = event.dateFrom\n  ? event.dateTo && event.dateFrom.toDateString() !== event.dateTo.toDateString()\n    ? `${formatDate(event.dateFrom)} – ${formatDate(event.dateTo!)}`\n    : formatDate(event.dateFrom)\n  : event.dateFromRaw || 'TBC';\n---\n\n<article class=\"event-card\">\n  <div class=\"event-card__header\">\n    <span class=\"state-pill\" data-state={event.state}>{event.state === 'VIRTUAL' ? 'Virtual' : event.state}</span>\n    {event.tags.slice(0, 3).map((tag) => (\n      <span class=\"tag-chip\">{tag}</span>\n    ))}\n  </div>\n  <div class=\"event-card__name\">\n    {event.url\n      ? <a href={event.url} target=\"_blank\" rel=\"noopener noreferrer\">{event.name}</a>\n      : event.name\n    }\n  </div>\n  <div class=\"event-card__date\">{dateStr}</div>\n  {(event.cfpOpenRaw || event.cfpCloseRaw) && (\n    <div class=\"event-card__cfp\">\n      {cfpOpen === true && (\n        <span class=\"cfp-open\">CFP open</span>\n      )}\n      {cfpOpen === false && cfpClosed && (\n        <span class=\"cfp-closed\">CFP closed</span>\n      )}\n      {cfpOpen === null && (event.cfpOpenRaw || event.cfpCloseRaw) && (\n        <span>CFP: {[event.cfpOpenRaw, event.cfpCloseRaw].filter(Boolean).join(' – ')}</span>\n      )}\n      {cfpOpen === false && !cfpClosed && event.cfpCloseRaw && (\n        <span>CFP closes {event.cfpCloseRaw}</span>\n      )}\n    </div>\n  )}\n</article>\n"
  },
  {
    "path": "website/src/components/EventTable.astro",
    "content": "---\nimport type { Event } from '@data/types';\nimport { formatDate } from '@data/parse-date';\n\ninterface Props {\n  events: Event[];\n}\n\nconst { events } = Astro.props;\n---\n\n{events.length === 0 ? (\n  <p class=\"empty-state\">No events found.</p>\n) : (\n  <div class=\"data-table__wrap\">\n    <table class=\"data-table\">\n      <thead>\n        <tr>\n          <th>Event</th>\n          <th>State</th>\n          <th>Date</th>\n          <th>Tags</th>\n        </tr>\n      </thead>\n      <tbody>\n        {events.map((e) => {\n          const dateStr = e.dateFrom\n            ? e.dateTo && e.dateFrom.toDateString() !== e.dateTo.toDateString()\n              ? `${formatDate(e.dateFrom)} – ${formatDate(e.dateTo!)}`\n              : formatDate(e.dateFrom)\n            : e.dateFromRaw || 'TBC';\n\n          return (\n            <tr>\n              <td>\n                {e.url\n                  ? <a href={e.url} target=\"_blank\" rel=\"noopener noreferrer\">{e.name}</a>\n                  : e.name\n                }\n              </td>\n              <td><span class=\"state-pill\" data-state={e.state}>{e.state === 'VIRTUAL' ? 'Virtual' : e.state}</span></td>\n              <td style=\"white-space: nowrap\">{dateStr}</td>\n              <td>\n                <div style=\"display:flex;flex-wrap:wrap;gap:0.3rem\">\n                  {e.tags.map((tag) => <span class=\"tag-chip\">{tag}</span>)}\n                </div>\n              </td>\n            </tr>\n          );\n        })}\n      </tbody>\n    </table>\n  </div>\n)}\n"
  },
  {
    "path": "website/src/components/FilterIsland.tsx",
    "content": "import { useState } from 'preact/hooks';\nimport type { Event, StateCode } from '@data/types';\n\ninterface Props {\n  events: Event[];\n}\n\nconst STATE_LABELS: Record<string, string> = {\n  ACT: 'ACT', NSW: 'NSW', NT: 'NT', QLD: 'QLD',\n  SA: 'SA', TAS: 'TAS', VIC: 'VIC', WA: 'WA',\n  VIRTUAL: 'Virtual', ALL: 'All', OTH: 'Other',\n};\n\n// Astro serializes Date objects as ISO strings — coerce back to Date\nfunction toDate(val: unknown): Date | null {\n  if (!val) return null;\n  if (val instanceof Date) return isNaN(val.getTime()) ? null : val;\n  const d = new Date(val as string);\n  return isNaN(d.getTime()) ? null : d;\n}\n\nfunction fmt(val: unknown): string {\n  const d = toDate(val);\n  if (!d) return '';\n  return d.toLocaleDateString('en-AU', { day: 'numeric', month: 'short', year: 'numeric' });\n}\n\nfunction fuzzyMatch(query: string, target: string): boolean {\n  const q = query.toLowerCase();\n  const t = target.toLowerCase();\n  let qi = 0;\n  for (let ti = 0; ti < t.length && qi < q.length; ti++) {\n    if (t[ti] === q[qi]) qi++;\n  }\n  return qi === q.length;\n}\n\nexport default function FilterIsland({ events }: Props) {\n  const [search, setSearch] = useState('');\n  const [selectedStates, setSelectedStates] = useState<StateCode[]>([]);\n  const [selectedTags, setSelectedTags] = useState<string[]>([]);\n  const [cfpOnly, setCfpOnly] = useState(false);\n\n  const now = new Date();\n\n  const allStates = [...new Set(events.map((e) => e.state))].sort() as StateCode[];\n  const allTags = [...new Set(events.flatMap((e) => e.tags))].sort();\n  const hasCfpData = events.some((e) => { const d = toDate(e.cfpClose); return d && d >= now; });\n\n  const filtered = events.filter((e) => {\n    if (search.trim()) {\n      const q = search.trim();\n      const searchable = [e.name, STATE_LABELS[e.state] ?? e.state, ...e.tags].join(' ');\n      if (!fuzzyMatch(q, searchable)) return false;\n    }\n    if (selectedStates.length > 0 && !selectedStates.includes(e.state)) return false;\n    if (selectedTags.length > 0 && !e.tags.some((t) => selectedTags.includes(t))) return false;\n    if (cfpOnly) {\n      const cfpClose = toDate(e.cfpClose);\n      if (!cfpClose || now > cfpClose) return false;\n    }\n    return true;\n  });\n\n  function toggleState(s: StateCode) {\n    setSelectedStates((prev) =>\n      prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]\n    );\n  }\n\n  function toggleTag(t: string) {\n    setSelectedTags((prev) =>\n      prev.includes(t) ? prev.filter((x) => x !== t) : [...prev, t]\n    );\n  }\n\n  function clearAll() {\n    setSearch('');\n    setSelectedStates([]);\n    setSelectedTags([]);\n    setCfpOnly(false);\n  }\n\n  const hasFilters = search.trim() !== '' || selectedStates.length > 0 || selectedTags.length > 0 || cfpOnly;\n\n  return (\n    <div>\n      {/* Search */}\n      <div style={{ marginBottom: '1rem' }}>\n        <input\n          type=\"search\"\n          placeholder=\"Search events…\"\n          value={search}\n          onInput={(e) => setSearch((e.target as HTMLInputElement).value)}\n          style={{\n            width: '100%',\n            padding: '0.5rem 0.75rem',\n            fontSize: '0.95rem',\n            border: '1.5px solid var(--color-border)',\n            borderRadius: 'var(--radius)',\n            background: 'var(--color-bg)',\n            color: 'var(--color-text)',\n            fontFamily: 'inherit',\n            boxSizing: 'border-box',\n          }}\n        />\n      </div>\n\n      {/* Filters */}\n      <div style={{ marginBottom: '1.5rem' }}>\n        {allStates.length > 0 && (\n          <div style={{ marginBottom: '0.75rem' }}>\n            <div style={{ fontSize: '0.78rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--color-text-muted)', marginBottom: '0.4rem' }}>\n              State\n            </div>\n            <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.4rem' }} role=\"group\" aria-label=\"Filter by state\">\n              {allStates.map((s) => {\n                const active = selectedStates.includes(s);\n                return (\n                  <button\n                    key={s}\n                    onClick={() => toggleState(s)}\n                    aria-pressed={active}\n                    style={{\n                      padding: '4px 12px',\n                      borderRadius: '9999px',\n                      border: `2px solid ${active ? 'var(--color-accent)' : 'var(--color-border)'}`,\n                      background: active ? 'var(--color-accent)' : 'var(--color-surface)',\n                      color: active ? '#fff' : 'var(--color-text)',\n                      fontWeight: 600,\n                      fontSize: '0.85rem',\n                      cursor: 'pointer',\n                      fontFamily: 'inherit',\n                      minHeight: '32px',\n                    }}\n                  >\n                    {active ? '✓ ' : ''}{STATE_LABELS[s] ?? s}\n                  </button>\n                );\n              })}\n            </div>\n          </div>\n        )}\n\n        {allTags.length > 0 && (\n          <div style={{ marginBottom: '0.75rem' }}>\n            <div style={{ fontSize: '0.78rem', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.05em', color: 'var(--color-text-muted)', marginBottom: '0.4rem' }}>\n              Topic\n            </div>\n            <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.4rem' }} role=\"group\" aria-label=\"Filter by topic\">\n              {allTags.map((t) => {\n                const active = selectedTags.includes(t);\n                return (\n                  <button\n                    key={t}\n                    onClick={() => toggleTag(t)}\n                    aria-pressed={active}\n                    style={{\n                      padding: '3px 10px',\n                      borderRadius: '9999px',\n                      border: `2px solid ${active ? 'var(--color-accent)' : 'var(--color-border)'}`,\n                      background: active ? 'var(--color-accent)' : '#f9fafb',\n                      color: active ? '#fff' : 'var(--color-text-muted)',\n                      fontWeight: 600,\n                      fontSize: '0.8rem',\n                      cursor: 'pointer',\n                      fontFamily: 'inherit',\n                      minHeight: '28px',\n                    }}\n                  >\n                    {active ? '✓ ' : ''}{t}\n                  </button>\n                );\n              })}\n            </div>\n          </div>\n        )}\n\n        <div style={{ display: 'flex', alignItems: 'center', gap: '1rem', flexWrap: 'wrap' }}>\n          {hasCfpData && (\n            <label style={{ display: 'flex', alignItems: 'center', gap: '0.4rem', cursor: 'pointer', fontSize: '0.9rem' }}>\n              <input\n                type=\"checkbox\"\n                checked={cfpOnly}\n                onChange={(e) => setCfpOnly((e.target as HTMLInputElement).checked)}\n              />\n              CFP open now\n            </label>\n          )}\n          {hasFilters && (\n            <button\n              onClick={clearAll}\n              style={{\n                background: 'none', border: 'none', cursor: 'pointer',\n                color: 'var(--color-text-muted)', fontSize: '0.85rem',\n                textDecoration: 'underline', padding: 0, fontFamily: 'inherit',\n              }}\n            >\n              Clear filters\n            </button>\n          )}\n        </div>\n      </div>\n\n      {/* Results count */}\n      <div style={{ fontSize: '0.85rem', color: 'var(--color-text-muted)', marginBottom: '1rem' }}>\n        {filtered.length} event{filtered.length !== 1 ? 's' : ''}\n        {hasFilters ? ' matching filters' : ''}\n      </div>\n\n      {/* Event cards */}\n      {filtered.length === 0 ? (\n        <div class=\"empty-state\">\n          <p>{hasFilters ? 'No events match your filters.' : 'No upcoming events right now.'}</p>\n          {hasFilters && (\n            <button\n              onClick={clearAll}\n              style={{\n                background: 'var(--color-accent)', color: '#fff',\n                border: 'none', borderRadius: '8px',\n                padding: '0.5rem 1.25rem', cursor: 'pointer',\n                fontSize: '0.9rem', fontFamily: 'inherit',\n                marginTop: '0.5rem',\n              }}\n            >\n              Clear filters\n            </button>\n          )}\n        </div>\n      ) : (\n        <div class=\"card-grid\">\n          {filtered.map((e) => {\n            const cfpClose = toDate(e.cfpClose);\n            const dateFrom = toDate(e.dateFrom);\n            const dateTo = toDate(e.dateTo);\n\n            const cfpIsOpen = cfpClose ? now <= cfpClose : null;\n\n            const dateStr = dateFrom\n              ? dateTo && dateFrom.toDateString() !== dateTo.toDateString()\n                ? `${fmt(dateFrom)} – ${fmt(dateTo)}`\n                : fmt(dateFrom)\n              : (e.dateFromRaw as string) || 'TBC';\n\n            return (\n              <article key={`${e.name}-${e.dateFromRaw}`} class=\"event-card\">\n                <div class=\"event-card__header\">\n                  <span class=\"state-pill\" data-state={e.state}>\n                    {e.state === 'VIRTUAL' ? 'Virtual' : e.state}\n                  </span>\n                  {(e.tags as string[]).slice(0, 3).map((tag) => (\n                    <span key={tag} class=\"tag-chip\">{tag}</span>\n                  ))}\n                </div>\n                <div class=\"event-card__name\">\n                  {e.url\n                    ? <a href={e.url as string} target=\"_blank\" rel=\"noopener noreferrer\">{e.name as string}</a>\n                    : e.name as string\n                  }\n                </div>\n                <div class=\"event-card__date\">{dateStr}</div>\n                {(e.cfpOpenRaw || e.cfpCloseRaw) && (\n                  <div class=\"event-card__cfp\">\n                    {cfpIsOpen === true && <span class=\"cfp-open\">CFP open</span>}\n                    {cfpIsOpen === false && <span class=\"cfp-closed\">CFP closed</span>}\n                    {cfpIsOpen === null && (e.cfpOpenRaw || e.cfpCloseRaw) && (\n                      <span>CFP: {[e.cfpOpenRaw, e.cfpCloseRaw].filter(Boolean).join(' – ')}</span>\n                    )}\n                  </div>\n                )}\n              </article>\n            );\n          })}\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "website/src/components/Footer.astro",
    "content": "---\nconst year = new Date().getFullYear();\n---\n\n<footer class=\"footer\">\n  <div class=\"container footer__inner\">\n    <span>\n      &copy; {year} DevEvents &mdash; Licensed under\n      <a href=\"https://creativecommons.org/publicdomain/zero/1.0/\" target=\"_blank\" rel=\"noopener noreferrer\">CC0</a>\n    </span>\n    <span>\n      <a href=\"https://github.com/vatsalyagoel/DevEvents\" target=\"_blank\" rel=\"noopener noreferrer\">GitHub</a>\n      &middot;\n      <a href=\"/about\">About</a>\n      &middot;\n      <a href=\"https://github.com/vatsalyagoel/DevEvents/issues/new\" target=\"_blank\" rel=\"noopener noreferrer\">Report an issue</a>\n    </span>\n  </div>\n</footer>\n"
  },
  {
    "path": "website/src/components/Nav.astro",
    "content": "---\ninterface Props {\n  currentPath: string;\n}\n\nconst { currentPath } = Astro.props;\n\nconst links = [\n  { href: '/events', label: 'Events' },\n  { href: '/meetups', label: 'Meetups' },\n  { href: '/about', label: 'About' },\n];\n---\n\n<nav class=\"nav\">\n  <div class=\"container nav__inner\">\n    <a href=\"/\" class=\"nav__logo\">DevEvents</a>\n    <ul class=\"nav__links\">\n      {links.map(({ href, label }) => (\n        <li>\n          <a\n            href={href}\n            class={currentPath.startsWith(href) ? 'active' : ''}\n          >\n            {label}\n          </a>\n        </li>\n      ))}\n    </ul>\n    <a\n      href=\"https://github.com/vatsalyagoel/DevEvents/edit/main/README.md\"\n      class=\"nav__cta\"\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n    >\n      Add Event\n    </a>\n    <button class=\"theme-toggle\" aria-label=\"Toggle dark mode\" onclick=\"toggleTheme()\"></button>\n  </div>\n</nav>\n\n<script is:inline>\n  function toggleTheme() {\n    var next = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark';\n    document.documentElement.dataset.theme = next;\n    localStorage.setItem('theme', next);\n  }\n</script>\n"
  },
  {
    "path": "website/src/components/UpcomingEventsIsland.tsx",
    "content": "import type { Event } from '@data/types';\n\ninterface Props {\n  events: Event[];\n}\n\nfunction toDate(val: unknown): Date | null {\n  if (!val) return null;\n  if (val instanceof Date) return isNaN(val.getTime()) ? null : val;\n  const d = new Date(val as string);\n  return isNaN(d.getTime()) ? null : d;\n}\n\nfunction fmt(val: unknown): string {\n  const d = toDate(val);\n  if (!d) return '';\n  return d.toLocaleDateString('en-AU', { day: 'numeric', month: 'short', year: 'numeric' });\n}\n\nexport default function UpcomingEventsIsland({ events }: Props) {\n  const now = new Date();\n\n  if (events.length === 0) {\n    return (\n      <div class=\"empty-state\">\n        <p>No upcoming events right now.</p>\n        <p>\n          <a href=\"/events/past\">Browse past events &rarr;</a>\n        </p>\n      </div>\n    );\n  }\n\n  return (\n    <div class=\"card-grid\">\n      {events.map((e) => {\n        const dateFrom = toDate(e.dateFrom);\n        const dateTo = toDate(e.dateTo);\n        const cfpClose = toDate(e.cfpClose);\n        const cfpIsOpen = cfpClose ? now <= cfpClose : null;\n\n        const dateStr = dateFrom\n          ? dateTo && dateFrom.toDateString() !== dateTo.toDateString()\n            ? `${fmt(dateFrom)} – ${fmt(dateTo)}`\n            : fmt(dateFrom)\n          : (e.dateFromRaw as string) || 'TBC';\n\n        return (\n          <article key={`${e.name}-${e.dateFromRaw}`} class=\"event-card\">\n            <div class=\"event-card__header\">\n              <span class=\"state-pill\" data-state={e.state}>\n                {e.state === 'VIRTUAL' ? 'Virtual' : e.state}\n              </span>\n              {e.tags.slice(0, 3).map((tag) => (\n                <span key={tag} class=\"tag-chip\">{tag}</span>\n              ))}\n            </div>\n            <div class=\"event-card__name\">\n              {e.url\n                ? <a href={e.url} target=\"_blank\" rel=\"noopener noreferrer\">{e.name}</a>\n                : e.name\n              }\n            </div>\n            <div class=\"event-card__date\">{dateStr}</div>\n            {(e.cfpOpenRaw || e.cfpCloseRaw) && (\n              <div class=\"event-card__cfp\">\n                {cfpIsOpen === true && <span class=\"cfp-open\">CFP open</span>}\n                {cfpIsOpen === false && <span class=\"cfp-closed\">CFP closed</span>}\n                {cfpIsOpen === null && (\n                  <span>CFP: {[e.cfpOpenRaw, e.cfpCloseRaw].filter(Boolean).join(' – ')}</span>\n                )}\n              </div>\n            )}\n          </article>\n        );\n      })}\n    </div>\n  );\n}\n"
  },
  {
    "path": "website/src/data/normalize-state.ts",
    "content": "import type { StateCode } from './types.js';\n\nconst STATE_MAP: Record<string, StateCode> = {\n  // Codes\n  act: 'ACT', nsw: 'NSW', nt: 'NT', qld: 'QLD',\n  sa: 'SA', tas: 'TAS', vic: 'VIC', wa: 'WA',\n  all: 'ALL', oth: 'OTH', other: 'OTH',\n  virtual: 'VIRTUAL', online: 'VIRTUAL',\n\n  // City names\n  canberra: 'ACT',\n  sydney: 'NSW', wollongong: 'NSW', newcastle: 'NSW',\n  darwin: 'NT',\n  brisbane: 'QLD', 'gold coast': 'QLD',\n  adelaide: 'SA',\n  hobart: 'TAS',\n  melbourne: 'VIC', geelong: 'VIC',\n  perth: 'WA',\n\n  // International / unknown\n  auckland: 'OTH', 'new zealand': 'OTH', global: 'OTH',\n};\n\nexport function normalizeState(raw: string): StateCode {\n  const key = raw.trim().toLowerCase();\n  return STATE_MAP[key] ?? 'OTH';\n}\n"
  },
  {
    "path": "website/src/data/parse-date.ts",
    "content": "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, oct: 9, nov: 10, dec: 11,\n};\n\nexport function parseEventDate(raw: string): Date | null {\n  if (!raw) return null;\n  const trimmed = raw.trim();\n  if (!trimmed || trimmed.toLowerCase() === 'tbc') return null;\n\n  // Normalise \"Sept\" → \"Sep\", \"June\" → \"Jun\", etc.\n  const normalised = trimmed.replace(/\\b(sept)\\b/i, 'Sep');\n\n  // Match d-Mmm-yyyy or dd-Mmm-yyyy\n  const match = normalised.match(/^(\\d{1,2})-([A-Za-z]{3})-(\\d{4})$/);\n  if (!match) return null;\n\n  const day = parseInt(match[1], 10);\n  const monthKey = match[2].toLowerCase();\n  const year = parseInt(match[3], 10);\n\n  const month = MONTH_MAP[monthKey];\n  if (month === undefined) return null;\n  if (day === 0 || day > 31) return null;\n\n  const date = new Date(year, month, day);\n  // Validate the date didn't roll over (e.g. Feb 30)\n  if (date.getMonth() !== month) return null;\n\n  return date;\n}\n\nexport function formatDate(date: Date): string {\n  return date.toLocaleDateString('en-AU', {\n    day: 'numeric',\n    month: 'short',\n    year: 'numeric',\n  });\n}\n"
  },
  {
    "path": "website/src/data/parse-events.ts",
    "content": "import { readFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkGfm from 'remark-gfm';\nimport { toString as mdastToString } from 'mdast-util-to-string';\nimport type { Root, Table, TableRow } from 'mdast';\nimport type { Event } from './types.js';\nimport { parseEventDate } from './parse-date.js';\nimport { normalizeState } from './normalize-state.js';\n\nimport { resolve } from 'path';\nconst DATA_ROOT = resolve(process.cwd(), '..');\n\nfunction extractLink(cell: TableRow['children'][number]): { text: string; url: string | null } {\n  const text = mdastToString(cell).trim();\n  // Find first link node\n  const link = cell.children.find((n) => n.type === 'link') as { type: 'link'; url: string } | undefined;\n  return { text, url: link?.url ?? null };\n}\n\nfunction parseTags(raw: string): string[] {\n  return raw\n    .split(/[,|]/)\n    .map((t) => t.trim())\n    .filter(Boolean);\n}\n\nfunction parseTableRows(table: Table, year: number, upcoming: boolean): Event[] {\n  const events: Event[] = [];\n  // Skip header row (index 0)\n  for (let i = 1; i < table.children.length; i++) {\n    const row = table.children[i];\n    if (!row || row.children.length < 7) continue;\n\n    const cells = row.children;\n    const { text: name, url } = extractLink(cells[0]);\n    if (!name) continue;\n\n    const stateRaw = mdastToString(cells[1]).trim();\n    const dateFromRaw = mdastToString(cells[2]).trim();\n    const dateToRaw = mdastToString(cells[3]).trim();\n    const cfpOpenRaw = mdastToString(cells[4]).trim();\n    const cfpCloseRaw = mdastToString(cells[5]).trim();\n    const tagsRaw = mdastToString(cells[6]).trim();\n\n    events.push({\n      name,\n      url,\n      state: normalizeState(stateRaw),\n      stateRaw,\n      dateFrom: parseEventDate(dateFromRaw),\n      dateTo: parseEventDate(dateToRaw),\n      dateFromRaw,\n      dateToRaw,\n      cfpOpen: parseEventDate(cfpOpenRaw),\n      cfpClose: parseEventDate(cfpCloseRaw),\n      cfpOpenRaw,\n      cfpCloseRaw,\n      tags: parseTags(tagsRaw),\n      year,\n      upcoming,\n    });\n  }\n  return events;\n}\n\nfunction parseMarkdown(content: string): Root {\n  return unified().use(remarkParse).use(remarkGfm).parse(content) as Root;\n}\n\nfunction getTablesFromAst(ast: Root): Table[] {\n  return ast.children.filter((n) => n.type === 'table') as Table[];\n}\n\nexport function parseUpcomingEvents(): Event[] {\n  const content = readFileSync(join(DATA_ROOT, 'README.md'), 'utf-8');\n  const ast = parseMarkdown(content);\n  const tables = getTablesFromAst(ast);\n  if (tables.length === 0) return [];\n  // README has one upcoming events table\n  const currentYear = new Date().getFullYear();\n  return parseTableRows(tables[0], currentYear, true);\n}\n\nexport function parsePastEvents(year: number): Event[] {\n  const filePath = join(DATA_ROOT, 'Past Events', `${year}.md`);\n  try {\n    const content = readFileSync(filePath, 'utf-8');\n    const ast = parseMarkdown(content);\n    const tables = getTablesFromAst(ast);\n    if (tables.length === 0) return [];\n    return parseTableRows(tables[0], year, false);\n  } catch {\n    // No archive file for this year — fall back to README for the current year\n    if (year === new Date().getFullYear()) {\n      return parseUpcomingEvents().map((e) => ({ ...e, upcoming: false }));\n    }\n    return [];\n  }\n}\n\nexport function getAvailableYears(): number[] {\n  try {\n    const dir = join(DATA_ROOT, 'Past Events');\n    const archivedYears = readdirSync(dir)\n      .filter((f) => /^\\d{4}\\.md$/.test(f))\n      .map((f) => parseInt(f, 10));\n\n    const currentYear = new Date().getFullYear();\n    const years = archivedYears.includes(currentYear)\n      ? archivedYears\n      : [currentYear, ...archivedYears];\n\n    return years.sort((a, b) => b - a); // newest first\n  } catch {\n    return [new Date().getFullYear()];\n  }\n}\n\nexport function getAllEvents(): Event[] {\n  const upcoming = parseUpcomingEvents();\n  const years = getAvailableYears();\n  const past = years.flatMap((y) => parsePastEvents(y));\n  return [...upcoming, ...past];\n}\n"
  },
  {
    "path": "website/src/data/parse-meetups.ts",
    "content": "import { readFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkGfm from 'remark-gfm';\nimport { toString as mdastToString } from 'mdast-util-to-string';\nimport type { Root, Table, TableRow, Heading } from 'mdast';\nimport type { Meetup, StateCode } from './types.js';\nimport { normalizeState } from './normalize-state.js';\n\nimport { resolve } from 'path';\nconst DATA_ROOT = resolve(process.cwd(), '..');\n\nconst STATE_FILE_MAP: Record<string, StateCode> = {\n  ACT: 'ACT', NSW: 'NSW', NT: 'NT', QLD: 'QLD',\n  SA: 'SA', TAS: 'TAS', VIC: 'VIC', WA: 'WA',\n};\n\nfunction extractLink(cell: TableRow['children'][number]): { text: string; url: string | null } {\n  const text = mdastToString(cell).trim();\n  const link = cell.children.find((n) => n.type === 'link') as { type: 'link'; url: string } | undefined;\n  return { text, url: link?.url ?? null };\n}\n\nfunction isRowEmpty(row: TableRow): boolean {\n  return row.children.every((cell) => mdastToString(cell).trim() === '');\n}\n\nfunction parseMeetupsFromFile(stateCode: StateCode, content: string): Meetup[] {\n  const ast = unified().use(remarkParse).use(remarkGfm).parse(content) as Root;\n  const meetups: Meetup[] = [];\n  let stale = false;\n\n  for (const node of ast.children) {\n    if (node.type === 'heading') {\n      const headingText = mdastToString(node as Heading).toLowerCase();\n      if (headingText.includes('stale')) {\n        stale = true;\n      }\n      continue;\n    }\n\n    if (node.type !== 'table') continue;\n\n    const table = node as Table;\n    const headerRow = table.children[0];\n    if (!headerRow) continue;\n    const colCount = headerRow.children.length;\n\n    for (let i = 1; i < table.children.length; i++) {\n      const row = table.children[i];\n      if (!row || isRowEmpty(row)) continue;\n\n      const { text: name, url } = extractLink(row.children[0]);\n      if (!name) continue;\n\n      if (stale || colCount === 1) {\n        // Stale meetups table — only name column\n        meetups.push({\n          name,\n          url,\n          location: '',\n          host: '',\n          frequency: '',\n          tags: [],\n          state: stateCode,\n          stale: true,\n        });\n      } else {\n        // Active meetups: Name | Location | Host | Frequency | Tags\n        const location = colCount > 1 ? mdastToString(row.children[1]).trim() : '';\n        const host = colCount > 2 ? mdastToString(row.children[2]).trim() : '';\n        const frequency = colCount > 3 ? mdastToString(row.children[3]).trim() : '';\n        const tagsRaw = colCount > 4 ? mdastToString(row.children[4]).trim() : '';\n        const tags = tagsRaw\n          .split(/[,|&]/)\n          .map((t) => t.trim().replace(/^[\"']|[\"']$/g, ''))\n          .filter(Boolean);\n\n        meetups.push({\n          name,\n          url,\n          location,\n          host,\n          frequency,\n          tags,\n          state: stateCode,\n          stale: false,\n        });\n      }\n    }\n  }\n\n  return meetups;\n}\n\nexport function parseMeetupsByState(state: string): Meetup[] {\n  const stateCode = STATE_FILE_MAP[state.toUpperCase()];\n  if (!stateCode) return [];\n\n  const filePath = join(DATA_ROOT, 'Meetups', `${state.toUpperCase()}.md`);\n  try {\n    const content = readFileSync(filePath, 'utf-8');\n    return parseMeetupsFromFile(stateCode, content);\n  } catch {\n    return [];\n  }\n}\n\nexport function getAllMeetups(): Meetup[] {\n  const dir = join(DATA_ROOT, 'Meetups');\n  try {\n    const files = readdirSync(dir).filter((f) => f.endsWith('.md'));\n    return files.flatMap((f) => {\n      const stateCode = f.replace('.md', '') as StateCode;\n      const content = readFileSync(join(dir, f), 'utf-8');\n      return parseMeetupsFromFile(stateCode, content);\n    });\n  } catch {\n    return [];\n  }\n}\n\nexport function getAvailableStates(): StateCode[] {\n  return Object.keys(STATE_FILE_MAP) as StateCode[];\n}\n"
  },
  {
    "path": "website/src/data/types.ts",
    "content": "export type StateCode =\n  | 'ACT' | 'NSW' | 'NT' | 'QLD' | 'SA' | 'TAS' | 'VIC' | 'WA'\n  | 'ALL' | 'OTH' | 'VIRTUAL';\n\nexport interface Event {\n  name: string;\n  url: string | null;\n  state: StateCode;\n  stateRaw: string;\n  dateFrom: Date | null;\n  dateTo: Date | null;\n  dateFromRaw: string;\n  dateToRaw: string;\n  cfpOpen: Date | null;\n  cfpClose: Date | null;\n  cfpOpenRaw: string;\n  cfpCloseRaw: string;\n  tags: string[];\n  year: number;\n  upcoming: boolean;\n}\n\nexport interface Meetup {\n  name: string;\n  url: string | null;\n  location: string;\n  host: string;\n  frequency: string;\n  tags: string[];\n  state: StateCode;\n  stale: boolean;\n}\n"
  },
  {
    "path": "website/src/env.d.ts",
    "content": "/// <reference types=\"astro/client\" />\n/// <reference path=\"../.astro/types.d.ts\" />"
  },
  {
    "path": "website/src/layouts/Base.astro",
    "content": "---\nimport Nav from '@components/Nav.astro';\nimport Footer from '@components/Footer.astro';\nimport '../styles/global.css';\n\ninterface Props {\n  title: string;\n  description?: string;\n}\n\nconst { title, description = 'A community-maintained list of Australian developer events and meetups.' } = Astro.props;\nconst canonicalURL = new URL(Astro.url.pathname, Astro.site);\n---\n\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <script is:inline>\n      (function() {\n        var saved = localStorage.getItem('theme');\n        var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n        document.documentElement.dataset.theme = saved || (prefersDark ? 'dark' : 'light');\n      })();\n    </script>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <title>{title === 'DevEvents' ? title : `${title} | DevEvents`}</title>\n    <meta name=\"description\" content={description} />\n    <link rel=\"canonical\" href={canonicalURL} />\n\n    <!-- Open Graph -->\n    <meta property=\"og:type\" content=\"website\" />\n    <meta property=\"og:url\" content={canonicalURL} />\n    <meta property=\"og:title\" content={title} />\n    <meta property=\"og:description\" content={description} />\n    <meta property=\"og:site_name\" content=\"DevEvents\" />\n\n    <!-- Favicon -->\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\n  </head>\n  <body>\n    <Nav currentPath={Astro.url.pathname} />\n    <main>\n      <slot />\n    </main>\n    <Footer />\n  </body>\n</html>\n"
  },
  {
    "path": "website/src/pages/404.astro",
    "content": "---\nimport Base from '@layouts/Base.astro';\n---\n\n<Base title=\"Page Not Found\">\n  <div class=\"container\">\n    <div style=\"text-align:center; padding: 5rem 1rem;\">\n      <div style=\"font-size:4rem; font-weight:800; color:var(--color-border); line-height:1;\">404</div>\n      <h1 style=\"font-size:1.5rem; margin:0.5rem 0 1rem;\">Page not found</h1>\n      <p style=\"color:var(--color-text-muted); margin-bottom:2rem;\">\n        This page doesn't exist. Try browsing events or meetups instead.\n      </p>\n      <div style=\"display:flex; gap:0.75rem; justify-content:center; flex-wrap:wrap;\">\n        <a href=\"/\" style=\"padding:0.55rem 1.25rem; background:var(--color-accent); color:#fff; border-radius:var(--radius); font-weight:600;\">\n          Go home\n        </a>\n        <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);\">\n          Browse events\n        </a>\n      </div>\n    </div>\n  </div>\n</Base>\n"
  },
  {
    "path": "website/src/pages/about.astro",
    "content": "---\nimport Base from '@layouts/Base.astro';\n---\n\n<Base\n  title=\"About\"\n  description=\"About DevEvents — a community-maintained list of Australian developer events.\"\n>\n  <div class=\"container\">\n    <div class=\"page-header\">\n      <h1>About DevEvents</h1>\n    </div>\n    <div class=\"page-content\" style=\"max-width:680px;\">\n      <p>\n        DevEvents is a community-maintained, open-source list of Australian developer events — conferences, workshops, and meetups of interest to software developers.\n      </p>\n      <p>\n        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.\n      </p>\n\n      <h2 style=\"font-size:1.2rem; margin-top:2rem;\">What gets listed?</h2>\n      <ul>\n        <li>Australian-based events from 2016 onwards</li>\n        <li>Events of interest to the software development community (technical, leadership, agile, AI, etc.)</li>\n        <li>In-person and virtual events; free and paid</li>\n      </ul>\n\n      <h2 style=\"font-size:1.2rem; margin-top:2rem;\">How to contribute</h2>\n      <p>\n        The simplest way is to use the GitHub editor:\n      </p>\n      <ol>\n        <li>Open <a href=\"https://github.com/vatsalyagoel/DevEvents/edit/main/README.md\" target=\"_blank\" rel=\"noopener noreferrer\">README.md on GitHub</a></li>\n        <li>Add your event to the table in chronological order</li>\n        <li>Submit a pull request</li>\n      </ol>\n      <p>\n        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.\n      </p>\n\n      <h2 style=\"font-size:1.2rem; margin-top:2rem;\">License</h2>\n      <p>\n        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.\n      </p>\n\n      <p style=\"margin-top:2rem;\">\n        <a href=\"https://github.com/vatsalyagoel/DevEvents\" target=\"_blank\" rel=\"noopener noreferrer\">View on GitHub &rarr;</a>\n      </p>\n    </div>\n  </div>\n</Base>\n"
  },
  {
    "path": "website/src/pages/events/index.astro",
    "content": "---\nimport Base from '@layouts/Base.astro';\nimport FilterIsland from '@components/FilterIsland';\nimport { parseUpcomingEvents } from '@data/parse-events';\n\nconst allUpcoming = parseUpcomingEvents();\nconst today = new Date();\ntoday.setHours(0, 0, 0, 0);\nconst upcoming = allUpcoming.filter((e) => {\n  const end = e.dateTo ?? e.dateFrom;\n  if (!end) return true;\n  return end >= today;\n});\n---\n\n<Base\n  title=\"Upcoming Events\"\n  description={`${upcoming.length} upcoming Australian developer events. Filter by state, topic, and CFP status.`}\n>\n  <div class=\"container\">\n    <div class=\"page-header\">\n      <div class=\"breadcrumb\">\n        <span>Events</span>\n      </div>\n      <div style=\"display:flex; align-items:flex-start; justify-content:space-between; flex-wrap:wrap; gap:0.75rem;\">\n        <div>\n          <h1 style=\"margin:0\">Upcoming Events</h1>\n          <p style=\"color: var(--color-text-muted); margin: 0.4rem 0 0;\">\n            <a href=\"/events/past\">Browse past events &rarr;</a>\n          </p>\n        </div>\n        <a\n          href=\"https://github.com/vatsalyagoel/DevEvents/edit/main/README.md\"\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          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;\"\n        >\n          Edit on GitHub\n        </a>\n      </div>\n    </div>\n    <div class=\"page-content\">\n      {upcoming.length === 0 ? (\n        <div class=\"empty-state\">\n          <p>No upcoming events listed yet.</p>\n          <p>\n            <a\n              href=\"https://github.com/vatsalyagoel/DevEvents/edit/main/README.md\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n            >\n              Add the first one &rarr;\n            </a>\n          </p>\n        </div>\n      ) : (\n        <FilterIsland events={upcoming} client:load />\n      )}\n    </div>\n  </div>\n</Base>\n"
  },
  {
    "path": "website/src/pages/events/past/[year].astro",
    "content": "---\nimport Base from '@layouts/Base.astro';\nimport EventTable from '@components/EventTable.astro';\nimport { getAvailableYears, parsePastEvents } from '@data/parse-events';\n\nexport async function getStaticPaths() {\n  const years = getAvailableYears();\n  return years.map((year) => ({\n    params: { year: String(year) },\n    props: { year, events: parsePastEvents(year) },\n  }));\n}\n\ninterface Props {\n  year: number;\n  events: Awaited<ReturnType<typeof parsePastEvents>>;\n}\n\nconst { year } = Astro.props;\nlet { events } = Astro.props;\nconst allYears = getAvailableYears();\nconst currentYear = new Date().getFullYear();\nif (year === currentYear) {\n  const today = new Date();\n  today.setHours(0, 0, 0, 0);\n  events = events.filter((e) => {\n    const end = e.dateTo ?? e.dateFrom;\n    return end !== null && end < today;\n  });\n}\nconst currentIndex = allYears.indexOf(year);\nconst prevYear = allYears[currentIndex + 1];\nconst nextYear = allYears[currentIndex - 1];\nconst editUrl = year === currentYear\n  ? 'https://github.com/vatsalyagoel/DevEvents/edit/main/README.md'\n  : `https://github.com/vatsalyagoel/DevEvents/edit/main/Past%20Events/${year}.md`;\n---\n\n<Base\n  title={`${year} Events`}\n  description={`Australian developer events in ${year}. ${events.length} events listed.`}\n>\n  <div class=\"container\">\n    <div class=\"page-header\">\n      <div class=\"breadcrumb\">\n        <a href=\"/events\">Events</a>\n        <span class=\"breadcrumb__sep\">/</span>\n        <a href=\"/events/past\">Past Events</a>\n        <span class=\"breadcrumb__sep\">/</span>\n        <span>{year}</span>\n      </div>\n      <div style=\"display:flex; align-items:flex-start; justify-content:space-between; flex-wrap:wrap; gap:0.75rem;\">\n        <div>\n          <h1 style=\"margin:0\">{year} Events</h1>\n          <p style=\"color: var(--color-text-muted); margin: 0.4rem 0 0;\">\n            {events.length} event{events.length !== 1 ? 's' : ''} listed\n          </p>\n        </div>\n        <a\n          href={editUrl}\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          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;\"\n        >\n          Edit on GitHub\n        </a>\n      </div>\n    </div>\n    <div class=\"page-content\">\n      <EventTable events={events} />\n\n      <div style=\"display:flex; gap:1rem; margin-top:2rem; font-size:0.9rem; flex-wrap:wrap;\">\n        {prevYear && <a href={`/events/past/${prevYear}`}>&larr; {prevYear}</a>}\n        {nextYear && <a href={`/events/past/${nextYear}`}>{nextYear} &rarr;</a>}\n      </div>\n    </div>\n  </div>\n</Base>\n"
  },
  {
    "path": "website/src/pages/events/past/index.astro",
    "content": "---\nimport Base from '@layouts/Base.astro';\nimport { getAvailableYears } from '@data/parse-events';\n\nconst years = getAvailableYears();\n---\n\n<Base title=\"Past Events\" description=\"Archive of Australian developer events from 2016 to present.\">\n  <div class=\"container\">\n    <div class=\"page-header\">\n      <div class=\"breadcrumb\">\n        <a href=\"/events\">Events</a>\n        <span class=\"breadcrumb__sep\">/</span>\n        <span>Past Events</span>\n      </div>\n      <h1>Past Events</h1>\n      <p style=\"color: var(--color-text-muted); margin: 0.5rem 0 0;\">Australian developer events archived by year.</p>\n    </div>\n    <div class=\"page-content\">\n      <div class=\"year-grid\">\n        {years.map((year) => (\n          <a href={`/events/past/${year}`} class=\"year-card\">{year}</a>\n        ))}\n      </div>\n    </div>\n  </div>\n</Base>\n"
  },
  {
    "path": "website/src/pages/index.astro",
    "content": "---\nimport Base from '@layouts/Base.astro';\nimport UpcomingEventsIsland from '@components/UpcomingEventsIsland';\nimport { parseUpcomingEvents } from '@data/parse-events';\nimport { getAvailableStates, parseMeetupsByState } from '@data/parse-meetups';\n\nconst allUpcoming = parseUpcomingEvents();\nconst today = new Date();\ntoday.setHours(0, 0, 0, 0);\nconst upcoming = allUpcoming.filter((e) => {\n  const end = e.dateTo ?? e.dateFrom;\n  if (!end) return true;\n  return end >= today;\n});\n\nconst states = getAvailableStates();\nconst meetupCounts = states.map((s) => ({\n  state: s,\n  count: parseMeetupsByState(s).filter((m) => !m.stale).length,\n})).filter(({ count }) => count > 0);\n---\n\n<Base\n  title=\"DevEvents\"\n  description=\"A community-maintained list of Australian developer events and meetups.\"\n>\n  <!-- Hero -->\n  <section class=\"hero\">\n    <div class=\"container\">\n      <h1 class=\"hero__title\">\n        Australian<br/><span>Developer Events</span>\n      </h1>\n      <p class=\"hero__sub\">\n        A community-maintained, open-source list of conferences, workshops, and meetups for software developers in Australia.\n      </p>\n      <div style=\"display:flex; gap:0.75rem; justify-content:center; flex-wrap:wrap;\">\n        <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;\">\n          Browse Events\n        </a>\n        <a href=\"https://github.com/vatsalyagoel/DevEvents/edit/main/README.md\" target=\"_blank\" rel=\"noopener noreferrer\"\n           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);\">\n          Add an Event\n        </a>\n      </div>\n    </div>\n  </section>\n\n  <!-- Upcoming events -->\n  <section style=\"padding-bottom: 3rem;\">\n    <div class=\"container\">\n      <div style=\"display:flex; align-items:baseline; justify-content:space-between; flex-wrap:wrap; gap:0.5rem; margin-bottom:1.25rem;\">\n        <h2 class=\"section-heading\" style=\"margin:0\">Upcoming Events</h2>\n        <a href=\"/events\" style=\"font-size:0.9rem;\">View all &rarr;</a>\n      </div>\n\n      <UpcomingEventsIsland events={upcoming} client:load />\n    </div>\n  </section>\n\n  <!-- Meetups teaser -->\n  {meetupCounts.length > 0 && (\n    <section style=\"background:var(--color-surface); border-top:1px solid var(--color-border); border-bottom:1px solid var(--color-border); padding:2.5rem 0;\">\n      <div class=\"container\">\n        <div style=\"display:flex; align-items:baseline; justify-content:space-between; flex-wrap:wrap; gap:0.5rem; margin-bottom:1.25rem;\">\n          <h2 class=\"section-heading\" style=\"margin:0\">Regular Meetups</h2>\n          <a href=\"/meetups\" style=\"font-size:0.9rem;\">All meetups &rarr;</a>\n        </div>\n        <div style=\"display:flex; flex-wrap:wrap; gap:0.6rem;\">\n          {meetupCounts.map(({ state, count }) => (\n            <a\n              href={`/meetups/${state.toLowerCase()}`}\n              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);\"\n            >\n              <span class=\"state-pill\" data-state={state}>{state}</span>\n              {count} meetup{count !== 1 ? 's' : ''}\n            </a>\n          ))}\n        </div>\n      </div>\n    </section>\n  )}\n\n  <!-- Contribute CTA -->\n  <section style=\"padding: 3.5rem 0;\">\n    <div class=\"container\" style=\"text-align:center;\">\n      <h2 style=\"font-size:1.4rem; font-weight:700; margin-bottom:0.75rem;\">Know of an event we missed?</h2>\n      <p style=\"color:var(--color-text-muted); margin-bottom:1.5rem; max-width:480px; margin-left:auto; margin-right:auto;\">\n        This list is maintained by the community. You can add or update events directly on GitHub — no coding required.\n      </p>\n      <a\n        href=\"https://github.com/vatsalyagoel/DevEvents\"\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        style=\"padding:0.65rem 1.5rem; background:var(--color-accent); color:#fff; border-radius:var(--radius); font-weight:600;\"\n      >\n        Contribute on GitHub\n      </a>\n    </div>\n  </section>\n</Base>\n"
  },
  {
    "path": "website/src/pages/meetups/[state].astro",
    "content": "---\nimport Base from '@layouts/Base.astro';\nimport { getAvailableStates, parseMeetupsByState } from '@data/parse-meetups';\nimport type { StateCode } from '@data/types';\n\nexport async function getStaticPaths() {\n  const states = getAvailableStates();\n  return states.map((state) => ({\n    params: { state: state.toLowerCase() },\n    props: { state, meetups: parseMeetupsByState(state) },\n  }));\n}\n\ninterface Props {\n  state: StateCode;\n  meetups: Awaited<ReturnType<typeof parseMeetupsByState>>;\n}\n\nconst STATE_NAMES: Record<StateCode, string> = {\n  ACT: 'Australian Capital Territory',\n  NSW: 'New South Wales',\n  NT: 'Northern Territory',\n  QLD: 'Queensland',\n  SA: 'South Australia',\n  TAS: 'Tasmania',\n  VIC: 'Victoria',\n  WA: 'Western Australia',\n  ALL: 'All States',\n  OTH: 'Other',\n  VIRTUAL: 'Virtual',\n};\n\nconst { state, meetups } = Astro.props;\nconst active = meetups.filter((m) => !m.stale);\nconst stale = meetups.filter((m) => m.stale);\nconst stateName = STATE_NAMES[state] ?? state;\nconst editUrl = `https://github.com/vatsalyagoel/DevEvents/edit/main/Meetups/${state}.md`;\n---\n\n<Base\n  title={`${state} Meetups`}\n  description={`Developer meetups in ${stateName}. ${active.length} active meetup${active.length !== 1 ? 's' : ''} listed.`}\n>\n  <div class=\"container\">\n    <div class=\"page-header\">\n      <div class=\"breadcrumb\">\n        <a href=\"/meetups\">Meetups</a>\n        <span class=\"breadcrumb__sep\">/</span>\n        <span>{state}</span>\n      </div>\n      <div style=\"display:flex; align-items:flex-start; justify-content:space-between; flex-wrap:wrap; gap:0.75rem;\">\n        <h1 style=\"margin:0\">\n          <span class=\"state-pill\" data-state={state} style=\"font-size:0.75rem;vertical-align:middle\">{state}</span>\n          &nbsp;{stateName} Meetups\n        </h1>\n        <a\n          href={editUrl}\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          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;\"\n        >\n          Edit on GitHub\n        </a>\n      </div>\n    </div>\n    <div class=\"page-content\">\n      {active.length === 0 ? (\n        <div class=\"empty-state\">\n          <p>No meetups currently listed for {stateName}.</p>\n          <p>\n            <a href={editUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n              Add one on GitHub &rarr;\n            </a>\n          </p>\n        </div>\n      ) : (\n        <div class=\"data-table__wrap\" style=\"margin-bottom: 2rem;\">\n          <table class=\"data-table\">\n            <thead>\n              <tr>\n                <th>Meetup</th>\n                <th>Location</th>\n                <th>Host</th>\n                <th>Frequency</th>\n                <th>Tags</th>\n              </tr>\n            </thead>\n            <tbody>\n              {active.map((m) => (\n                <tr>\n                  <td>\n                    {m.url\n                      ? <a href={m.url} target=\"_blank\" rel=\"noopener noreferrer\">{m.name}</a>\n                      : m.name\n                    }\n                  </td>\n                  <td>{m.location}</td>\n                  <td>{m.host}</td>\n                  <td>{m.frequency}</td>\n                  <td>\n                    <div style=\"display:flex;flex-wrap:wrap;gap:0.3rem\">\n                      {m.tags.map((t) => <span class=\"tag-chip\">{t}</span>)}\n                    </div>\n                  </td>\n                </tr>\n              ))}\n            </tbody>\n          </table>\n        </div>\n      )}\n\n      {stale.length > 0 && (\n        <details>\n          <summary style=\"cursor:pointer; color: var(--color-text-muted); font-size:0.9rem; margin-bottom:1rem;\">\n            {stale.length} stale meetup{stale.length !== 1 ? 's' : ''} (no longer running)\n          </summary>\n          <div class=\"data-table__wrap\" style=\"margin-top:0.75rem;\">\n            <table class=\"data-table\">\n              <thead>\n                <tr><th>Meetup</th></tr>\n              </thead>\n              <tbody>\n                {stale.map((m) => (\n                  <tr>\n                    <td>\n                      {m.url\n                        ? <a href={m.url} target=\"_blank\" rel=\"noopener noreferrer\">{m.name}</a>\n                        : m.name\n                      }\n                    </td>\n                  </tr>\n                ))}\n              </tbody>\n            </table>\n          </div>\n        </details>\n      )}\n    </div>\n  </div>\n</Base>\n"
  },
  {
    "path": "website/src/pages/meetups/index.astro",
    "content": "---\nimport Base from '@layouts/Base.astro';\nimport { getAvailableStates, parseMeetupsByState } from '@data/parse-meetups';\nimport type { StateCode } from '@data/types';\n\nconst STATE_NAMES: Record<StateCode, string> = {\n  ACT: 'Australian Capital Territory',\n  NSW: 'New South Wales',\n  NT: 'Northern Territory',\n  QLD: 'Queensland',\n  SA: 'South Australia',\n  TAS: 'Tasmania',\n  VIC: 'Victoria',\n  WA: 'Western Australia',\n  ALL: 'All States',\n  OTH: 'Other',\n  VIRTUAL: 'Virtual',\n};\n\nconst states = getAvailableStates();\nconst stateData = states.map((state) => {\n  const meetups = parseMeetupsByState(state);\n  const active = meetups.filter((m) => !m.stale);\n  return { state, name: STATE_NAMES[state] ?? state, count: active.length };\n});\n---\n\n<Base\n  title=\"Meetups\"\n  description=\"Regular developer meetups across Australian states and territories.\"\n>\n  <div class=\"container\">\n    <div class=\"page-header\">\n      <div class=\"breadcrumb\">\n        <span>Meetups</span>\n      </div>\n      <h1>Meetups</h1>\n      <p style=\"color: var(--color-text-muted); margin: 0.5rem 0 0;\">\n        Regular developer meetups by state. Want to add one?\n        <a href=\"https://github.com/vatsalyagoel/DevEvents\" target=\"_blank\" rel=\"noopener noreferrer\">Contribute on GitHub &rarr;</a>\n      </p>\n    </div>\n    <div class=\"page-content\">\n      <div class=\"state-grid\">\n        {stateData.map(({ state, name, count }) => (\n          <a href={`/meetups/${state.toLowerCase()}`} class=\"state-card\">\n            <div class=\"state-card__name\">\n              <span class=\"state-pill\" data-state={state}>{state}</span>\n              &nbsp;{name}\n            </div>\n            <div class=\"state-card__count\">\n              {count > 0 ? `${count} active meetup${count !== 1 ? 's' : ''}` : 'No meetups listed'}\n            </div>\n          </a>\n        ))}\n      </div>\n    </div>\n  </div>\n</Base>\n"
  },
  {
    "path": "website/src/styles/global.css",
    "content": ":root {\n  --color-bg: #fafafa;\n  --color-surface: #ffffff;\n  --color-border: #e5e7eb;\n  --color-text: #111827;\n  --color-text-muted: #6b7280;\n  --color-accent: #0c7c5f;\n  --color-accent-light: #ecfdf5;\n  --color-accent-hover: #096649;\n  --color-subtle: #f9fafb;\n  --color-tag-bg: #f3f4f6;\n\n  /* State colours */\n  --state-act: #7c3aed;\n  --state-nsw: #1d4ed8;\n  --state-nt: #b45309;\n  --state-qld: #b91c1c;\n  --state-sa: #0369a1;\n  --state-tas: #065f46;\n  --state-vic: #0c7c5f;\n  --state-wa: #c2410c;\n  --state-all: #4b5563;\n  --state-oth: #9ca3af;\n  --state-virtual: #6d28d9;\n\n  --radius: 8px;\n  --shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);\n  --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.08), 0 2px 4px -2px rgba(0,0,0,0.05);\n\n  --font: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;\n  --font-mono: ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace;\n}\n\n*, *::before, *::after { box-sizing: border-box; }\n\nhtml { font-size: 16px; }\n\nbody {\n  margin: 0;\n  font-family: var(--font);\n  background: var(--color-bg);\n  color: var(--color-text);\n  line-height: 1.6;\n  -webkit-font-smoothing: antialiased;\n}\n\na { color: var(--color-accent); text-decoration: none; }\na:hover { text-decoration: underline; }\n\nh1, h2, h3, h4 { line-height: 1.2; margin: 0 0 0.5em; }\n\n.container {\n  max-width: 1100px;\n  margin: 0 auto;\n  padding: 0 1.5rem;\n}\n\n/* State pill */\n.state-pill {\n  display: inline-block;\n  padding: 2px 8px;\n  border-radius: 9999px;\n  font-size: 0.7rem;\n  font-weight: 700;\n  letter-spacing: 0.05em;\n  text-transform: uppercase;\n  color: #fff;\n  background: var(--state-oth);\n}\n.state-pill[data-state=\"ACT\"] { background: var(--state-act); }\n.state-pill[data-state=\"NSW\"] { background: var(--state-nsw); }\n.state-pill[data-state=\"NT\"]  { background: var(--state-nt); }\n.state-pill[data-state=\"QLD\"] { background: var(--state-qld); }\n.state-pill[data-state=\"SA\"]  { background: var(--state-sa); }\n.state-pill[data-state=\"TAS\"] { background: var(--state-tas); }\n.state-pill[data-state=\"VIC\"] { background: var(--state-vic); }\n.state-pill[data-state=\"WA\"]  { background: var(--state-wa); }\n.state-pill[data-state=\"ALL\"] { background: var(--state-all); }\n.state-pill[data-state=\"VIRTUAL\"] { background: var(--state-virtual); }\n\n/* Tag chip */\n.tag-chip {\n  display: inline-block;\n  padding: 2px 8px;\n  border-radius: 9999px;\n  font-size: 0.72rem;\n  background: var(--color-tag-bg);\n  color: var(--color-text-muted);\n  border: 1px solid var(--color-border);\n}\n\n/* Event card */\n.event-card {\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius);\n  padding: 1.25rem 1.5rem;\n  box-shadow: var(--shadow);\n  display: flex;\n  flex-direction: column;\n  gap: 0.5rem;\n  transition: box-shadow 0.15s ease, border-color 0.15s ease;\n}\n.event-card:hover {\n  box-shadow: var(--shadow-md);\n  border-color: var(--color-border);\n}\n.event-card__header {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  flex-wrap: wrap;\n}\n.event-card__name {\n  font-size: 1.05rem;\n  font-weight: 600;\n  color: var(--color-text);\n}\n.event-card__name a { color: var(--color-text); }\n.event-card__name a:hover { color: var(--color-accent); text-decoration: none; }\n.event-card__date {\n  font-size: 0.9rem;\n  color: var(--color-text-muted);\n}\n.event-card__cfp {\n  font-size: 0.82rem;\n  color: var(--color-text-muted);\n}\n.event-card__cfp .cfp-open {\n  color: var(--color-accent);\n  font-weight: 600;\n}\n.event-card__cfp .cfp-closed {\n  color: var(--color-text-muted);\n}\n.event-card__tags {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 0.3rem;\n  margin-top: 0.25rem;\n}\n\n/* Grid of cards */\n.card-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n  gap: 1.25rem;\n}\n\n/* Tables */\n.data-table {\n  width: 100%;\n  border-collapse: collapse;\n  font-size: 0.9rem;\n}\n.data-table th {\n  text-align: left;\n  padding: 0.6rem 0.75rem;\n  background: var(--color-subtle);\n  border-bottom: 2px solid var(--color-border);\n  font-size: 0.78rem;\n  text-transform: uppercase;\n  letter-spacing: 0.05em;\n  color: var(--color-text-muted);\n}\n.data-table td {\n  padding: 0.65rem 0.75rem;\n  border-bottom: 1px solid var(--color-border);\n  vertical-align: top;\n}\n.data-table tr:last-child td { border-bottom: none; }\n.data-table tr:hover td { background: var(--color-subtle); }\n.data-table__wrap {\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius);\n  overflow: hidden;\n  box-shadow: var(--shadow);\n  overflow-x: auto;\n}\n\n/* Nav */\n.nav {\n  background: var(--color-surface);\n  border-bottom: 1px solid var(--color-border);\n  position: sticky;\n  top: 0;\n  z-index: 100;\n}\n.nav__inner {\n  display: flex;\n  align-items: center;\n  gap: 1.5rem;\n  height: 56px;\n}\n.nav__logo {\n  font-weight: 800;\n  font-size: 1.1rem;\n  color: var(--color-accent);\n  letter-spacing: -0.02em;\n  flex-shrink: 0;\n}\n.nav__logo:hover { text-decoration: none; }\n.nav__links {\n  display: flex;\n  gap: 1.25rem;\n  list-style: none;\n  margin: 0;\n  padding: 0;\n  flex: 1;\n}\n.nav__links a {\n  font-size: 0.9rem;\n  color: var(--color-text-muted);\n  font-weight: 500;\n}\n.nav__links a:hover, .nav__links a.active { color: var(--color-text); text-decoration: none; }\n.nav__cta {\n  padding: 0.4rem 0.9rem;\n  background: var(--color-accent);\n  color: #fff !important;\n  border-radius: var(--radius);\n  font-size: 0.85rem;\n  font-weight: 600;\n  white-space: nowrap;\n}\n.nav__cta:hover { background: var(--color-accent-hover) !important; text-decoration: none !important; }\n\n/* Hero */\n.hero {\n  padding: 4rem 0 3rem;\n  text-align: center;\n}\n.hero__title {\n  font-size: clamp(2rem, 5vw, 3rem);\n  font-weight: 800;\n  letter-spacing: -0.03em;\n  margin-bottom: 0.75rem;\n}\n.hero__title span { color: var(--color-accent); }\n.hero__sub {\n  font-size: 1.1rem;\n  color: var(--color-text-muted);\n  max-width: 520px;\n  margin: 0 auto 2rem;\n}\n\n/* Section headings */\n.section-heading {\n  font-size: 1.4rem;\n  font-weight: 700;\n  margin-bottom: 1.25rem;\n  letter-spacing: -0.01em;\n}\n\n/* Empty state */\n.empty-state {\n  text-align: center;\n  padding: 3rem 1rem;\n  color: var(--color-text-muted);\n}\n\n/* Footer */\n.footer {\n  border-top: 1px solid var(--color-border);\n  padding: 2rem 0;\n  margin-top: 4rem;\n  font-size: 0.85rem;\n  color: var(--color-text-muted);\n}\n.footer__inner {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  flex-wrap: wrap;\n  gap: 1rem;\n}\n.footer a { color: var(--color-text-muted); }\n.footer a:hover { color: var(--color-text); }\n\n/* Year grid */\n.year-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));\n  gap: 1rem;\n}\n.year-card {\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius);\n  padding: 1.5rem 1rem;\n  text-align: center;\n  box-shadow: var(--shadow);\n  color: var(--color-text);\n  font-weight: 700;\n  font-size: 1.3rem;\n  transition: box-shadow 0.15s, border-color 0.15s;\n}\n.year-card:hover {\n  box-shadow: var(--shadow-md);\n  border-color: var(--color-accent);\n  text-decoration: none;\n  color: var(--color-accent);\n}\n\n/* State overview grid */\n.state-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));\n  gap: 1rem;\n}\n.state-card {\n  background: var(--color-surface);\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius);\n  padding: 1.25rem;\n  box-shadow: var(--shadow);\n  display: flex;\n  flex-direction: column;\n  gap: 0.4rem;\n  transition: box-shadow 0.15s, border-color 0.15s;\n  color: var(--color-text);\n}\n.state-card:hover {\n  box-shadow: var(--shadow-md);\n  border-color: var(--color-accent);\n  text-decoration: none;\n}\n.state-card__name { font-weight: 700; font-size: 1.1rem; }\n.state-card__count { font-size: 0.85rem; color: var(--color-text-muted); }\n\n/* Breadcrumb */\n.breadcrumb {\n  font-size: 0.85rem;\n  color: var(--color-text-muted);\n  margin-bottom: 1.5rem;\n  display: flex;\n  gap: 0.4rem;\n  align-items: center;\n  flex-wrap: wrap;\n}\n.breadcrumb a { color: var(--color-text-muted); }\n.breadcrumb a:hover { color: var(--color-text); }\n.breadcrumb__sep { color: var(--color-border); }\n\n/* Page layout */\n.page-header {\n  padding: 2.5rem 0 1.5rem;\n}\n.page-header h1 {\n  font-size: clamp(1.5rem, 3vw, 2rem);\n  font-weight: 800;\n  letter-spacing: -0.02em;\n}\n.page-content {\n  padding-bottom: 3rem;\n}\n\n@media (max-width: 640px) {\n  .nav__links { gap: 0.75rem; }\n  .card-grid { grid-template-columns: 1fr; }\n  .footer__inner { flex-direction: column; text-align: center; }\n}\n\n/* Dark mode */\n@media (prefers-color-scheme: dark) {\n  :root:not([data-theme=\"light\"]) {\n    --color-bg: #111827;\n    --color-surface: #1f2937;\n    --color-border: #374151;\n    --color-text: #f9fafb;\n    --color-text-muted: #9ca3af;\n    --color-accent: #10b981;\n    --color-accent-light: #064e3b;\n    --color-accent-hover: #34d399;\n    --color-subtle: #1f2937;\n    --color-tag-bg: #374151;\n  }\n}\n[data-theme=\"dark\"] {\n  --color-bg: #111827;\n  --color-surface: #1f2937;\n  --color-border: #374151;\n  --color-text: #f9fafb;\n  --color-text-muted: #9ca3af;\n  --color-accent: #10b981;\n  --color-accent-light: #064e3b;\n  --color-accent-hover: #34d399;\n  --color-subtle: #1f2937;\n  --color-tag-bg: #374151;\n}\n\n/* Theme toggle button */\n.theme-toggle {\n  background: none;\n  border: 1px solid var(--color-border);\n  border-radius: var(--radius);\n  cursor: pointer;\n  width: 32px;\n  height: 32px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 1rem;\n  color: var(--color-text-muted);\n  flex-shrink: 0;\n  padding: 0;\n  transition: border-color 0.15s, color 0.15s;\n}\n.theme-toggle:hover { border-color: var(--color-accent); color: var(--color-accent); }\n[data-theme=\"dark\"] .theme-toggle::after,\n:root:not([data-theme]) .theme-toggle::after { content: '☀'; }\n[data-theme=\"light\"] .theme-toggle::after { content: '☾'; }\n@media (prefers-color-scheme: light) {\n  :root:not([data-theme]) .theme-toggle::after { content: '☾'; }\n}\n"
  },
  {
    "path": "website/tsconfig.json",
    "content": "{\n  \"extends\": \"astro/tsconfigs/strict\",\n  \"compilerOptions\": {\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"preact\",\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@data/*\": [\"src/data/*\"],\n      \"@components/*\": [\"src/components/*\"],\n      \"@layouts/*\": [\"src/layouts/*\"]\n    }\n  }\n}\n"
  }
]