Showing preview only (1,662K chars total). Download the full file or copy to clipboard to get everything.
Repository: remoteintech/remote-jobs
Branch: main
Commit: bf76c82f4ba6
Files: 1039
Total size: 1.4 MB
Directory structure:
gitextract_lztpi08t/
├── .eleventyignore
├── .env-sample
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── config.yml
│ │ └── new_company.md
│ ├── PULL_REQUEST_TEMPLATE.MD
│ ├── dependabot.yml
│ ├── scripts/
│ │ └── validate-companies.js
│ └── workflows/
│ ├── ci.yml
│ ├── codeql-analysis.yml
│ └── validate-companies.yml
├── .gitignore
├── .nojekyll
├── .nvmrc
├── .prettierignore
├── .prettierrc
├── CLAUDE.md
├── LICENSE
├── README.md
├── eleventy.config.js
├── package.json
├── src/
│ ├── _config/
│ │ ├── collections.js
│ │ ├── events/
│ │ │ ├── build-css.js
│ │ │ ├── build-js.js
│ │ │ └── svg-to-jpeg.js
│ │ ├── events.js
│ │ ├── filters/
│ │ │ ├── dates.js
│ │ │ ├── fileExists.js
│ │ │ ├── markdown-format.js
│ │ │ ├── slugify.js
│ │ │ ├── sort-alphabetic.js
│ │ │ ├── sort-random.js
│ │ │ ├── split.js
│ │ │ ├── splitlines.js
│ │ │ └── striptags.js
│ │ ├── filters.js
│ │ ├── plugins/
│ │ │ ├── drafts.js
│ │ │ ├── html-config.js
│ │ │ └── markdown.js
│ │ ├── plugins.js
│ │ ├── setup/
│ │ │ ├── create-colors.js
│ │ │ └── generate-favicons.js
│ │ ├── shortcodes/
│ │ │ ├── image.js
│ │ │ └── svg.js
│ │ ├── shortcodes.js
│ │ └── utils/
│ │ ├── clamp-generator.js
│ │ └── tokens-to-tailwind.js
│ ├── _data/
│ │ ├── changelog.json
│ │ ├── companyHelpers.js
│ │ ├── designTokens/
│ │ │ ├── borderRadius.json
│ │ │ ├── colors.json
│ │ │ ├── colorsBase.json
│ │ │ ├── fonts.json
│ │ │ ├── spacing.json
│ │ │ ├── textLeading.json
│ │ │ ├── textSizes.json
│ │ │ ├── textWeights.json
│ │ │ └── viewports.json
│ │ ├── github.js
│ │ ├── helpers.js
│ │ ├── labels.js
│ │ ├── meta.js
│ │ ├── navigation.js
│ │ └── personal.js
│ ├── _includes/
│ │ ├── head/
│ │ │ ├── css-inline.njk
│ │ │ ├── js-defer.njk
│ │ │ ├── js-inline.njk
│ │ │ ├── meta-info.njk
│ │ │ ├── preloads.njk
│ │ │ └── schema.njk
│ │ ├── partials/
│ │ │ ├── card-tag.njk
│ │ │ ├── company-card.njk
│ │ │ ├── date.njk
│ │ │ ├── details.njk
│ │ │ ├── edit-on.njk
│ │ │ ├── footer.njk
│ │ │ ├── header.njk
│ │ │ ├── main-nav.njk
│ │ │ ├── nav-search.njk
│ │ │ ├── pagination.njk
│ │ │ └── theme-switch.njk
│ │ ├── schemas/
│ │ │ ├── BlogPosting.njk
│ │ │ ├── BreadcrumbList.njk
│ │ │ ├── Organization.njk
│ │ │ └── WebSite.njk
│ │ └── webc/
│ │ ├── custom-card.webc
│ │ ├── custom-masonry.webc
│ │ ├── custom-peertube-link.webc
│ │ ├── custom-peertube.webc
│ │ ├── custom-svg.webc
│ │ ├── custom-youtube-link.webc
│ │ └── custom-youtube.webc
│ ├── _layouts/
│ │ ├── base.njk
│ │ ├── company.njk
│ │ ├── page.njk
│ │ ├── post.njk
│ │ └── tags.njk
│ ├── assets/
│ │ ├── css/
│ │ │ ├── global/
│ │ │ │ ├── base/
│ │ │ │ │ ├── fonts.css
│ │ │ │ │ ├── global-styles.css
│ │ │ │ │ ├── reset.css
│ │ │ │ │ └── variables.css
│ │ │ │ ├── blocks/
│ │ │ │ │ ├── button.css
│ │ │ │ │ ├── code.css
│ │ │ │ │ ├── company-card.css
│ │ │ │ │ ├── external-link.css
│ │ │ │ │ ├── main-nav.css
│ │ │ │ │ ├── prose.css
│ │ │ │ │ ├── section.css
│ │ │ │ │ ├── seperator.css
│ │ │ │ │ ├── site-footer.css
│ │ │ │ │ ├── site-logo.css
│ │ │ │ │ ├── skip-link.css
│ │ │ │ │ ├── textgradient.css
│ │ │ │ │ └── theme-switch.css
│ │ │ │ ├── compositions/
│ │ │ │ │ ├── cluster.css
│ │ │ │ │ ├── flow.css
│ │ │ │ │ ├── grid.css
│ │ │ │ │ ├── repel.css
│ │ │ │ │ ├── sidebar.css
│ │ │ │ │ └── wrapper.css
│ │ │ │ ├── global.css
│ │ │ │ ├── tests/
│ │ │ │ │ ├── debug.css
│ │ │ │ │ └── is-land.css
│ │ │ │ └── utilities/
│ │ │ │ ├── grayscale.css
│ │ │ │ ├── heading-line.css
│ │ │ │ ├── ontop.css
│ │ │ │ ├── region.css
│ │ │ │ ├── spin.css
│ │ │ │ └── visually-hidden.css
│ │ │ └── local/
│ │ │ ├── custom-card.css
│ │ │ ├── details.css
│ │ │ ├── footnotes.css
│ │ │ ├── forms.css
│ │ │ ├── gallery.css
│ │ │ ├── nav-main-drawer-cls.css
│ │ │ ├── pagination.css
│ │ │ ├── post.css
│ │ │ ├── styleguide.css
│ │ │ └── table.css
│ │ └── scripts/
│ │ ├── bundle/
│ │ │ ├── details.js
│ │ │ ├── dialog.js
│ │ │ ├── is-land.js
│ │ │ ├── nav-drawer.js
│ │ │ ├── nav-sub.js
│ │ │ └── theme-toggle.js
│ │ ├── components/
│ │ │ └── custom-masonry.js
│ │ └── outbound-tracking.js
│ ├── blog/
│ │ ├── 2017-10-03-announcing-remote-in-tech-company.md
│ │ ├── 2017-10-09-so-is-this-just-another-job-board.md
│ │ ├── 2017-12-06-popular-company-profiles-and-the-github-traffic-charts.md
│ │ ├── 2017-12-28-highest-view-count.md
│ │ ├── 2018-01-01-new-year-new-job.md
│ │ ├── 2018-01-06-advice-from-a-constant-job-seeker.md
│ │ ├── 2018-09-19-website-updates.md
│ │ ├── 2018-09-25-hacktoberfest-is-back.md
│ │ ├── 2026-01-13-the-big-redesign.md
│ │ ├── 2026-01-17-seo-improvements.md
│ │ ├── 2026-02-15-keeping-things-tidy.md
│ │ └── blog.json
│ ├── common/
│ │ ├── 404.njk
│ │ ├── _redirects.njk
│ │ ├── carbon.njk
│ │ ├── feed-atom.njk
│ │ ├── feed-json.njk
│ │ ├── humans.njk
│ │ ├── og-images.njk
│ │ ├── robots.njk
│ │ ├── site-manifest.njk
│ │ ├── sitemap.njk
│ │ ├── tagList.njk
│ │ └── tags.njk
│ ├── companies/
│ │ ├── 10up.md
│ │ ├── 15five.md
│ │ ├── 1password.md
│ │ ├── 37signals.md
│ │ ├── 3blocks.md
│ │ ├── 42-technologies.md
│ │ ├── 90-seconds.md
│ │ ├── a-1-auto-transport.md
│ │ ├── abiturma.md
│ │ ├── ably.md
│ │ ├── abstract.md
│ │ ├── acquia.md
│ │ ├── activecampaign.md
│ │ ├── ad-hoc.md
│ │ ├── adaface.md
│ │ ├── addstructure.md
│ │ ├── adeva.md
│ │ ├── adzuna.md
│ │ ├── aerolab.md
│ │ ├── aerostrat.md
│ │ ├── aestudio.md
│ │ ├── aha.md
│ │ ├── aiir.md
│ │ ├── aim-india.md
│ │ ├── airbyte.md
│ │ ├── airgarage.md
│ │ ├── airtreks.md
│ │ ├── aivitex.md
│ │ ├── akamai.md
│ │ ├── akka.md
│ │ ├── alami.md
│ │ ├── alan.md
│ │ ├── algorand.md
│ │ ├── algorithmia.md
│ │ ├── alice.md
│ │ ├── alight-solutions.md
│ │ ├── alley.md
│ │ ├── allydvm.md
│ │ ├── alphasights.md
│ │ ├── amazon.md
│ │ ├── ambaum.md
│ │ ├── andela.md
│ │ ├── animalz.md
│ │ ├── annertech.md
│ │ ├── anomali.md
│ │ ├── apartment-therapy.md
│ │ ├── appcues.md
│ │ ├── appen.md
│ │ ├── appinio.md
│ │ ├── applaudo.md
│ │ ├── applied-ai-company-aaico.md
│ │ ├── appstractor.md
│ │ ├── appwrite.md
│ │ ├── argyle.md
│ │ ├── arkency.md
│ │ ├── art-and-logic.md
│ │ ├── artefactual.md
│ │ ├── articulate.md
│ │ ├── asana.md
│ │ ├── astronomer.md
│ │ ├── atento.md
│ │ ├── atlassian.md
│ │ ├── atozdebug.md
│ │ ├── audiense.md
│ │ ├── aula.md
│ │ ├── auth0.md
│ │ ├── automattic.md
│ │ ├── axelerant.md
│ │ ├── axios.md
│ │ ├── bairesdev.md
│ │ ├── balena.md
│ │ ├── balsamiq.md
│ │ ├── bandcamp.md
│ │ ├── bandlab.md
│ │ ├── bandzoogle.md
│ │ ├── baremetrics.md
│ │ ├── basecamp.md
│ │ ├── bear-group.md
│ │ ├── bebanjo.md
│ │ ├── beenverified.md
│ │ ├── best-practical-solutions.md
│ │ ├── betable.md
│ │ ├── betapeak.md
│ │ ├── betterup.md
│ │ ├── beyond-company.md
│ │ ├── beyondpricing.md
│ │ ├── bharatpe.md
│ │ ├── big-cartel.md
│ │ ├── bill.md
│ │ ├── bilstein-group.md
│ │ ├── bit-zesty.md
│ │ ├── bitnami.md
│ │ ├── bitovi.md
│ │ ├── bizink.md
│ │ ├── blameless.md
│ │ ├── bloc.md
│ │ ├── bluecat-networks.md
│ │ ├── bluespark.md
│ │ ├── boldare.md
│ │ ├── bonsai.md
│ │ ├── bounteous.md
│ │ ├── brainstorm-force.md
│ │ ├── bright-funds.md
│ │ ├── brikl.md
│ │ ├── britecore.md
│ │ ├── broadwing.md
│ │ ├── buffer.md
│ │ ├── bugfender.md
│ │ ├── buysellads.md
│ │ ├── cabify.md
│ │ ├── calamari.md
│ │ ├── calendly.md
│ │ ├── calibre.md
│ │ ├── cancom.md
│ │ ├── canonical.md
│ │ ├── capchase.md
│ │ ├── capgemini.md
│ │ ├── capital-one.md
│ │ ├── capital-placement.md
│ │ ├── capmo.md
│ │ ├── carbon-black.md
│ │ ├── cards-against-humanity.md
│ │ ├── carecru.md
│ │ ├── caremessage.md
│ │ ├── carmatec.md
│ │ ├── cartodb.md
│ │ ├── cartstack.md
│ │ ├── casumo.md
│ │ ├── chainlink.md
│ │ ├── chargify.md
│ │ ├── charity-water.md
│ │ ├── chatgen.md
│ │ ├── checkly.md
│ │ ├── chef.md
│ │ ├── chess.md
│ │ ├── chroma.md
│ │ ├── circleci.md
│ │ ├── circonus.md
│ │ ├── civicactions.md
│ │ ├── civo.md
│ │ ├── clevertech.md
│ │ ├── clickup.md
│ │ ├── clootrack.md
│ │ ├── close.md
│ │ ├── cloudapp.md
│ │ ├── cloudera.md
│ │ ├── cloudlinux.md
│ │ ├── coalition-technologies.md
│ │ ├── code-b-solutions.md
│ │ ├── code-like-a-girl.md
│ │ ├── codea-it.md
│ │ ├── codepen.md
│ │ ├── codesandbox.md
│ │ ├── codeship.md
│ │ ├── codingcops.md
│ │ ├── cofense.md
│ │ ├── coforma.md
│ │ ├── cognizant.md
│ │ ├── coinbase.md
│ │ ├── coingape.md
│ │ ├── collabora.md
│ │ ├── comet.md
│ │ ├── companies.11tydata.js
│ │ ├── companies.json
│ │ ├── compucorp.md
│ │ ├── connexa.md
│ │ ├── consensys.md
│ │ ├── consumer-financial-protection-bureau.md
│ │ ├── continu.md
│ │ ├── conversio.md
│ │ ├── convert.md
│ │ ├── coodesh.md
│ │ ├── core-apps.md
│ │ ├── coreos.md
│ │ ├── corgibytes.md
│ │ ├── cosmic-chimps.md
│ │ ├── coursera.md
│ │ ├── cratedb.md
│ │ ├── crazygames.md
│ │ ├── creex-team.md
│ │ ├── crossover.md
│ │ ├── crowdstrike.md
│ │ ├── cueup.md
│ │ ├── customer-io.md
│ │ ├── cuvette.md
│ │ ├── cvs-health.md
│ │ ├── cwt.md
│ │ ├── cyber-whale.md
│ │ ├── daktronics.md
│ │ ├── dappradar.md
│ │ ├── darecode.md
│ │ ├── dashboardhub.md
│ │ ├── dashlane.md
│ │ ├── data-science-brigade.md
│ │ ├── data-science-dojo.md
│ │ ├── datacamp.md
│ │ ├── datadog.md
│ │ ├── datastax.md
│ │ ├── dave.md
│ │ ├── dealdash.md
│ │ ├── deel.md
│ │ ├── delighted.md
│ │ ├── designcode.md
│ │ ├── deskpass.md
│ │ ├── dev-spotlight.md
│ │ ├── devsquad.md
│ │ ├── dgraph.md
│ │ ├── digitalocean.md
│ │ ├── discord.md
│ │ ├── discourse.md
│ │ ├── dnsimple.md
│ │ ├── docker.md
│ │ ├── doist.md
│ │ ├── doit.md
│ │ ├── dow-jones.md
│ │ ├── dronedeploy.md
│ │ ├── dropbox.md
│ │ ├── drupal-jedi.md
│ │ ├── duckduckgo.md
│ │ ├── dynapictures.md
│ │ ├── eatstreet.md
│ │ ├── ebsco-information-services.md
│ │ ├── ebury.md
│ │ ├── eco-mind.md
│ │ ├── ecosmic.md
│ │ ├── edgar.md
│ │ ├── edify.md
│ │ ├── elastic.md
│ │ ├── elsewhen.md
│ │ ├── embraer.md
│ │ ├── employment-hero.md
│ │ ├── emsisoft.md
│ │ ├── encora.md
│ │ ├── engineyard.md
│ │ ├── enjoei.md
│ │ ├── enok.md
│ │ ├── entrision.md
│ │ ├── envato.md
│ │ ├── envoy.md
│ │ ├── epam.md
│ │ ├── epic-games.md
│ │ ├── epilocal.md
│ │ ├── episource.md
│ │ ├── epsy-health.md
│ │ ├── equal-experts-portugal.md
│ │ ├── ergeon.md
│ │ ├── estately.md
│ │ ├── etch.md
│ │ ├── etsy.md
│ │ ├── evelo.md
│ │ ├── evil-martians.md
│ │ ├── evrone.md
│ │ ├── exoscale.md
│ │ ├── exportdata.md
│ │ ├── extreme-networks.md
│ │ ├── eyeo.md
│ │ ├── factorialhr.md
│ │ ├── fairwinds.md
│ │ ├── faithlife.md
│ │ ├── fastly.md
│ │ ├── fdte.md
│ │ ├── fetlife.md
│ │ ├── ffw-agency.md
│ │ ├── figma.md
│ │ ├── filament-group.md
│ │ ├── findem.md
│ │ ├── findify.md
│ │ ├── fingerprint.md
│ │ ├── fire-engine-red.md
│ │ ├── firehire.md
│ │ ├── fis-global.md
│ │ ├── fiverr.md
│ │ ├── fivexl.md
│ │ ├── fleetio.md
│ │ ├── flexera.md
│ │ ├── flightaware.md
│ │ ├── flip.md
│ │ ├── flowing.md
│ │ ├── flowpath.md
│ │ ├── fly-io.md
│ │ ├── fmx.md
│ │ ├── focusnetworks.md
│ │ ├── foh-and-boh.md
│ │ ├── folotop.md
│ │ ├── formidable.md
│ │ ├── formstack.md
│ │ ├── four-kitchens.md
│ │ ├── fraudio.md
│ │ ├── freeagent.md
│ │ ├── freeletics.md
│ │ ├── fuel-made.md
│ │ ├── full-fabric.md
│ │ ├── functionize.md
│ │ ├── fyle.md
│ │ ├── gaggle.md
│ │ ├── geckoboard.md
│ │ ├── general-assembly.md
│ │ ├── geo-jobe.md
│ │ ├── gerencianet.md
│ │ ├── gft.md
│ │ ├── ghost-inspector.md
│ │ ├── giant-swarm.md
│ │ ├── giant.md
│ │ ├── gigsalad.md
│ │ ├── gitbook.md
│ │ ├── github.md
│ │ ├── gitlab.md
│ │ ├── gitprime.md
│ │ ├── glenn-website-design.md
│ │ ├── glitch.md
│ │ ├── gluware.md
│ │ ├── gmbapi.md
│ │ ├── godaddy.md
│ │ ├── gohiring.md
│ │ ├── gojob.md
│ │ ├── goldfire-agency.md
│ │ ├── gorman-health-group.md
│ │ ├── got-soccer.md
│ │ ├── grafana.md
│ │ ├── granicus.md
│ │ ├── graylog.md
│ │ ├── greenstitch-io.md
│ │ ├── gremlin.md
│ │ ├── gridium.md
│ │ ├── groove.md
│ │ ├── grou-ps.md
│ │ ├── grubhub.md
│ │ ├── gruntwork.md
│ │ ├── guidesmiths.md
│ │ ├── hack-reactor-remote.md
│ │ ├── hanzo.md
│ │ ├── happy-cog.md
│ │ ├── harris-consult.md
│ │ ├── harvest.md
│ │ ├── hashex.md
│ │ ├── hashicorp.md
│ │ ├── he-labs.md
│ │ ├── headforwards.md
│ │ ├── headway.md
│ │ ├── healthfinch.md
│ │ ├── heap.md
│ │ ├── heetch.md
│ │ ├── help-scout.md
│ │ ├── heroku.md
│ │ ├── hireology.md
│ │ ├── homeflic-wegrow.md
│ │ ├── homevalet.md
│ │ ├── honeybadger.md
│ │ ├── honeycomb.md
│ │ ├── hopper.md
│ │ ├── hotjar.md
│ │ ├── hubspot.md
│ │ ├── hubstaff.md
│ │ ├── hudl.md
│ │ ├── hugo.md
│ │ ├── human-made.md
│ │ ├── husl-digital.md
│ │ ├── hypergiant.md
│ │ ├── hyperion.md
│ │ ├── hypothesis.md
│ │ ├── i-stem.md
│ │ ├── ibm.md
│ │ ├── iclinic.md
│ │ ├── idonethis.md
│ │ ├── ifit.md
│ │ ├── igalia.md
│ │ ├── imagine-learning.md
│ │ ├── impala.md
│ │ ├── impira.md
│ │ ├── implisense.md
│ │ ├── incsub.md
│ │ ├── indrive.md
│ │ ├── infinite-red.md
│ │ ├── influxdata.md
│ │ ├── inquicker.md
│ │ ├── inshorts.md
│ │ ├── instamobile.md
│ │ ├── instructure.md
│ │ ├── intellum.md
│ │ ├── intent.md
│ │ ├── inter-link.md
│ │ ├── interactive-intelligence.md
│ │ ├── intercom.md
│ │ ├── interpersonal-frequency-i-f.md
│ │ ├── intevity.md
│ │ ├── intuit.md
│ │ ├── intuition-machines-inc.md
│ │ ├── invesco.md
│ │ ├── iohk.md
│ │ ├── iopipe.md
│ │ ├── ios-app-templates.md
│ │ ├── ipinfo.md
│ │ ├── ips-group-inc.md
│ │ ├── iqvia.md
│ │ ├── ironin.md
│ │ ├── iterative.md
│ │ ├── iwantmyname.md
│ │ ├── jackson-river.md
│ │ ├── jaya-tech.md
│ │ ├── jbs-custom-software-solutions.md
│ │ ├── jitbit.md
│ │ ├── jitera.md
│ │ ├── jobsity.md
│ │ ├── jolly-good-code.md
│ │ ├── joor.md
│ │ ├── journy-io.md
│ │ ├── joyent.md
│ │ ├── jupiterone.md
│ │ ├── kake.md
│ │ ├── karat.md
│ │ ├── kaufland-e-commerce.md
│ │ ├── kea.md
│ │ ├── keen-io.md
│ │ ├── kentik.md
│ │ ├── kestra.md
│ │ ├── khan-academy.md
│ │ ├── kickback-rewards-systems.md
│ │ ├── kindred.md
│ │ ├── kinsta.md
│ │ ├── kiprosh.md
│ │ ├── kissmetrics.md
│ │ ├── klanik.md
│ │ ├── klaviyo.md
│ │ ├── knack.md
│ │ ├── kodify.md
│ │ ├── koding.md
│ │ ├── komoot.md
│ │ ├── kona.md
│ │ ├── konkurenta.md
│ │ ├── kraken.md
│ │ ├── kuali.md
│ │ ├── labelbox.md
│ │ ├── lambda-school.md
│ │ ├── lambert-labs.md
│ │ ├── laterpay.md
│ │ ├── launch-potato.md
│ │ ├── leadership-success.md
│ │ ├── leadfeeder.md
│ │ ├── leadiq.md
│ │ ├── lets-encrypt.md
│ │ ├── lifen.md
│ │ ├── lifetime-value-company.md
│ │ ├── lightspeed.md
│ │ ├── linaro.md
│ │ ├── lincoln-loop.md
│ │ ├── line-plus-corporation.md
│ │ ├── link11.md
│ │ ├── linux-foundation.md
│ │ ├── lionsher.md
│ │ ├── litmus.md
│ │ ├── liveperson.md
│ │ ├── loadsys.md
│ │ ├── localistico.md
│ │ ├── locus-robotics.md
│ │ ├── logdna.md
│ │ ├── logdog.md
│ │ ├── logrocket.md
│ │ ├── lullabot.md
│ │ ├── luxoft.md
│ │ ├── luxor.md
│ │ ├── lyseon-tech.md
│ │ ├── lytx.md
│ │ ├── madewithlove.md
│ │ ├── madisoft.md
│ │ ├── mailerlite.md
│ │ ├── maintainnow.md
│ │ ├── manifold.md
│ │ ├── mapbox.md
│ │ ├── marco-polo.md
│ │ ├── mariadb.md
│ │ ├── marketade.md
│ │ ├── marsbased.md
│ │ ├── massive-pixel-creation.md
│ │ ├── mattermost.md
│ │ ├── maxicus.md
│ │ ├── mayven-studios.md
│ │ ├── mayvue.md
│ │ ├── meant4.md
│ │ ├── mediacurrent.md
│ │ ├── mediavine.md
│ │ ├── medium.md
│ │ ├── memberful.md
│ │ ├── memory.md
│ │ ├── mercari.md
│ │ ├── merico.md
│ │ ├── meridianlink.md
│ │ ├── messagebird.md
│ │ ├── metalab.md
│ │ ├── metamask.md
│ │ ├── meteorops.md
│ │ ├── microsoft.md
│ │ ├── mindful.md
│ │ ├── mixcloud.md
│ │ ├── mixmax.md
│ │ ├── mixrank.md
│ │ ├── mobile-jazz.md
│ │ ├── modern-health.md
│ │ ├── modern-tribe.md
│ │ ├── modsquad.md
│ │ ├── molteo.md
│ │ ├── mongodb.md
│ │ ├── monthly.md
│ │ ├── mozilla.md
│ │ ├── mtc.md
│ │ ├── muck-rack.md
│ │ ├── mux.md
│ │ ├── mvpmule.md
│ │ ├── mycelium.md
│ │ ├── mysql.md
│ │ ├── nagarro.md
│ │ ├── namecheap.md
│ │ ├── nationwide.md
│ │ ├── nearform.md
│ │ ├── nerdwallet.md
│ │ ├── netapp.md
│ │ ├── netguru.md
│ │ ├── netlify.md
│ │ ├── netris.md
│ │ ├── netsparker.md
│ │ ├── nettl-edinburgh.md
│ │ ├── new-context.md
│ │ ├── next.md
│ │ ├── no-code-no-problem.md
│ │ ├── nodesource.md
│ │ ├── noredink.md
│ │ ├── novoda.md
│ │ ├── npm.md
│ │ ├── nuage.md
│ │ ├── nuharbor-security.md
│ │ ├── nuna.md
│ │ ├── nvidia.md
│ │ ├── ocient.md
│ │ ├── octopus-deploy.md
│ │ ├── oddball.md
│ │ ├── okta.md
│ │ ├── olark.md
│ │ ├── olist.md
│ │ ├── ollie-order.md
│ │ ├── ollie.md
│ │ ├── olo.md
│ │ ├── ombu-labs.md
│ │ ├── omniti.md
│ │ ├── on-the-go-systems.md
│ │ ├── onna.md
│ │ ├── opencity-labs.md
│ │ ├── opencraft.md
│ │ ├── openzeppelin.md
│ │ ├── optoro.md
│ │ ├── oracle.md
│ │ ├── ordermentum.md
│ │ ├── oreilly-media.md
│ │ ├── oreilly-online-learning.md
│ │ ├── our-hometown-inc.md
│ │ ├── outsourcingdev.md
│ │ ├── over.md
│ │ ├── packlink.md
│ │ ├── pagepro.md
│ │ ├── pagerduty.md
│ │ ├── paktor.md
│ │ ├── palantir-net.md
│ │ ├── panther-labs.md
│ │ ├── parabol.md
│ │ ├── parexel.md
│ │ ├── park-assist.md
│ │ ├── parsely.md
│ │ ├── particular-software.md
│ │ ├── pathable.md
│ │ ├── payfully.md
│ │ ├── paylocity.md
│ │ ├── paypay.md
│ │ ├── payscale.md
│ │ ├── paytm-labs.md
│ │ ├── payu.md
│ │ ├── peachworks.md
│ │ ├── peakforce.md
│ │ ├── percona.md
│ │ ├── pex.md
│ │ ├── picpay.md
│ │ ├── pindrop.md
│ │ ├── plai.md
│ │ ├── platform-builders.md
│ │ ├── platform-sh.md
│ │ ├── pleo.md
│ │ ├── plex.md
│ │ ├── pnc-financial-services.md
│ │ ├── polygon.md
│ │ ├── positiwise.md
│ │ ├── powerschool.md
│ │ ├── pragma.md
│ │ ├── precision-nutrition.md
│ │ ├── predict-mobile.md
│ │ ├── prelude.md
│ │ ├── previousnext.md
│ │ ├── prezi.md
│ │ ├── prezly.md
│ │ ├── primer.md
│ │ ├── primotly.md
│ │ ├── prisma.md
│ │ ├── privacycloud.md
│ │ ├── procenge.md
│ │ ├── procurify.md
│ │ ├── progress-engine.md
│ │ ├── prominent-edge.md
│ │ ├── puppet.md
│ │ ├── pwc.md
│ │ ├── qatium.md
│ │ ├── quaderno.md
│ │ ├── quantify.md
│ │ ├── questdb.md
│ │ ├── quicktrials.md
│ │ ├── quora.md
│ │ ├── rackspace.md
│ │ ├── raft.md
│ │ ├── railscarma.md
│ │ ├── rainforest-qa.md
│ │ ├── rakuten-travel-xchange.md
│ │ ├── ramp.md
│ │ ├── reaction-commerce.md
│ │ ├── reactiveops.md
│ │ ├── realtimecrm.md
│ │ ├── rebelmouse.md
│ │ ├── reboot-studio.md
│ │ ├── recharge.md
│ │ ├── rechat.md
│ │ ├── recurly.md
│ │ ├── red-hat.md
│ │ ├── reddit.md
│ │ ├── redlio-designs.md
│ │ ├── redmonk.md
│ │ ├── redox.md
│ │ ├── reducer.md
│ │ ├── refundid.md
│ │ ├── reinteractive.md
│ │ ├── remote-garage.md
│ │ ├── remotebase.md
│ │ ├── remotemore.md
│ │ ├── renaissance.md
│ │ ├── rendr-software-group.md
│ │ ├── renofi.md
│ │ ├── replit.md
│ │ ├── research-square.md
│ │ ├── revolgy.md
│ │ ├── revolut.md
│ │ ├── roadpass-digital.md
│ │ ├── roadtrippers.md
│ │ ├── rocket-chat.md
│ │ ├── roundproxies.md
│ │ ├── rtcamp-solutions.md
│ │ ├── sadapay.md
│ │ ├── safeguard-global.md
│ │ ├── salesforce.md
│ │ ├── sandhills-development.md
│ │ ├── sardine-ai.md
│ │ ├── scalac.md
│ │ ├── scaloy.md
│ │ ├── scandit.md
│ │ ├── schnell-solutions-limited.md
│ │ ├── scopic-software.md
│ │ ├── scrapingbee.md
│ │ ├── scrapinghub.md
│ │ ├── scylladb.md
│ │ ├── seaplane.md
│ │ ├── seatgeek.md
│ │ ├── securityscorecard.md
│ │ ├── seeq.md
│ │ ├── semaphore.md
│ │ ├── sendwave.md
│ │ ├── serpapi.md
│ │ ├── server-density.md
│ │ ├── servmask.md
│ │ ├── session.md
│ │ ├── shareup.md
│ │ ├── shattered-silicon.md
│ │ ├── shippabo.md
│ │ ├── shogun.md
│ │ ├── shopify.md
│ │ ├── sigma-defense.md
│ │ ├── signeasy.md
│ │ ├── silverfin.md
│ │ ├── simplabs.md
│ │ ├── simpletexting.md
│ │ ├── six-to-start.md
│ │ ├── sketch.md
│ │ ├── skillcrush.md
│ │ ├── skillshare.md
│ │ ├── skyrocket-ventures.md
│ │ ├── slack.md
│ │ ├── smartcash.md
│ │ ├── smile.md
│ │ ├── smmile-digital.md
│ │ ├── smugmug.md
│ │ ├── socket-supply-co.md
│ │ ├── softwaremill.md
│ │ ├── sommo.md
│ │ ├── sonatype.md
│ │ ├── soostone.md
│ │ ├── soshace.md
│ │ ├── sourcegraph.md
│ │ ├── spoqa.md
│ │ ├── spotify.md
│ │ ├── spreaker.md
│ │ ├── spreedly.md
│ │ ├── spruce.md
│ │ ├── stack-exchange.md
│ │ ├── stairlin.md
│ │ ├── status.md
│ │ ├── stencil.md
│ │ ├── sticker-mule.md
│ │ ├── stitch-fix.md
│ │ ├── stoneco.md
│ │ ├── stormx.md
│ │ ├── strapi.md
│ │ ├── streamnative.md
│ │ ├── stripe.md
│ │ ├── studysoup.md
│ │ ├── superplayer-and-co.md
│ │ ├── surevine.md
│ │ ├── suse.md
│ │ ├── sutherland-cloudsource.md
│ │ ├── svix.md
│ │ ├── sweetrush.md
│ │ ├── swif-ai.md
│ │ ├── swiggy.md
│ │ ├── swimlane.md
│ │ ├── syde.md
│ │ ├── sysdig.md
│ │ ├── tag1-consulting.md
│ │ ├── taledo.md
│ │ ├── taplytics.md
│ │ ├── taskade.md
│ │ ├── tatvasoft.md
│ │ ├── taxjar.md
│ │ ├── teamflow.md
│ │ ├── teamsnap.md
│ │ ├── teamultra.md
│ │ ├── ted.md
│ │ ├── teleport.md
│ │ ├── telerik.md
│ │ ├── telestax.md
│ │ ├── tenable.md
│ │ ├── teravision-technologies.md
│ │ ├── test-double.md
│ │ ├── testgorilla.md
│ │ ├── the-crafters-lab.md
│ │ ├── the-ghost-foundation.md
│ │ ├── the-home-depot.md
│ │ ├── the-planet-group.md
│ │ ├── the-publisher-desk.md
│ │ ├── the-scale-factory.md
│ │ ├── the-wirecutter.md
│ │ ├── theoremone.md
│ │ ├── thinkful.md
│ │ ├── third-iron.md
│ │ ├── thorn.md
│ │ ├── three-movers.md
│ │ ├── thrive-market.md
│ │ ├── tide.md
│ │ ├── timespot.md
│ │ ├── tipe.md
│ │ ├── toast.md
│ │ ├── toggl.md
│ │ ├── toptal.md
│ │ ├── tower.md
│ │ ├── tractionboard.md
│ │ ├── transfeera.md
│ │ ├── transition-technologies-advanced-solutions.md
│ │ ├── transloadit.md
│ │ ├── travis-ci.md
│ │ ├── travis.md
│ │ ├── treehouse.md
│ │ ├── treez.md
│ │ ├── trello.md
│ │ ├── tribe.md
│ │ ├── truelogic.md
│ │ ├── trussworks.md
│ │ ├── tuft-and-needle.md
│ │ ├── turing.md
│ │ ├── turtlemint.md
│ │ ├── twilio.md
│ │ ├── two.md
│ │ ├── udacity.md
│ │ ├── uhuru.md
│ │ ├── uncapped.md
│ │ ├── upcell.md
│ │ ├── upwork-pro.md
│ │ ├── upworthy.md
│ │ ├── usaa.md
│ │ ├── ushahidi.md
│ │ ├── uship.md
│ │ ├── v0-report.md
│ │ ├── valimail.md
│ │ ├── varnish-software.md
│ │ ├── vast-limits.md
│ │ ├── veeva.md
│ │ ├── vercel.md
│ │ ├── verve-systems.md
│ │ ├── veryfi.md
│ │ ├── vgs.md
│ │ ├── viperdev.md
│ │ ├── virta-health.md
│ │ ├── vivo.md
│ │ ├── vmware.md
│ │ ├── voiio.md
│ │ ├── vox-media.md
│ │ ├── voxy.md
│ │ ├── vshn.md
│ │ ├── wallethub.md
│ │ ├── webdevstudios.md
│ │ ├── webfx.md
│ │ ├── webikon.md
│ │ ├── webrunners.md
│ │ ├── webscrapinghq.md
│ │ ├── wells-fargo.md
│ │ ├── wemake-services.md
│ │ ├── wemakemvp.md
│ │ ├── whitecap-seo.md
│ │ ├── whitespectre.md
│ │ ├── wikihow.md
│ │ ├── wikimedia-foundation.md
│ │ ├── wildbit.md
│ │ ├── willings-inc.md
│ │ ├── wingify.md
│ │ ├── wipro.md
│ │ ├── wirefuture.md
│ │ ├── wizeline.md
│ │ ├── wolfram.md
│ │ ├── wolverine-trading.md
│ │ ├── wombat-security.md
│ │ ├── wp-media.md
│ │ ├── wyeworks.md
│ │ ├── x-team.md
│ │ ├── xapo.md
│ │ ├── xp-inc.md
│ │ ├── xwp.md
│ │ ├── yahoo.md
│ │ ├── yandex.md
│ │ ├── yazio.md
│ │ ├── yodo1.md
│ │ ├── yonder.md
│ │ ├── you-are-launched.md
│ │ ├── you-need-a-budget.md
│ │ ├── youcanbook-me.md
│ │ ├── zamp.md
│ │ ├── zaneffi.md
│ │ ├── zapier.md
│ │ ├── zeit-io.md
│ │ ├── zemoso.md
│ │ ├── zenrows.md
│ │ ├── zignaly.md
│ │ ├── zip-co.md
│ │ ├── zolar.md
│ │ ├── zootools.md
│ │ └── zup.md
│ └── pages/
│ ├── about.md
│ ├── blog.njk
│ ├── changelog.njk
│ ├── companies.njk
│ ├── contact.md
│ ├── contributing.md
│ ├── index.njk
│ ├── privacy.md
│ ├── search.njk
│ ├── tag.njk
│ └── tags.njk
└── tailwind.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eleventyignore
================================================
# Eleventy ignore file
# Files and directories that Eleventy should not process
# Node modules
node_modules/
# README files (already being processed as markdown, might conflict)
README.md
# Migration plan (if you add it back to the repo)
MIGRATION_PLAN.md
# Test/temporary directories
company-profiles-test/
# Git files
.git/
.gitignore
# Editor files
.vscode/
.idea/
# OS files
.DS_Store
# Build artifacts
dist/
# Documentation that shouldn't be in the build
CONTRIBUTING.md
LICENSE
CHANGELOG.md
================================================
FILE: .env-sample
================================================
URL=http://localhost:8080
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement. Please mention @dougaitken or email him directly `remote at dougaitken dot co dot uk`
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
_This supersedes any previous version of the code of conduct._
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing to Remote In Tech
Thank you for your interest in contributing! This repository maintains a list of companies that offer remote work opportunities in tech.
## Quick Start
1. Fork the repository
2. Clone your fork locally
3. Create a new branch for your changes
4. Add or update a company profile in `src/companies/`
5. Test the site locally (optional but recommended)
6. Submit a pull request
## Adding a New Company
### Requirements
- You are an employee of the company (or can verify the information)
- Company directly hires employees (no bootcamps/freelance platforms)
- Company offers genuine remote work opportunities
- Company is in or around the tech industry
### Steps
1. **Create company file**: Add a new file at `src/companies/company-name.md`
2. **Add frontmatter**: Use the structured YAML format (see template below)
3. **Add content sections**: Fill in all required markdown sections
4. **Test locally**: Run `npm run build:11ty` to verify it builds
### Company Profile Template
Create a new markdown file in `src/companies/` with this format:
```markdown
---
title: "Company Name"
slug: company-name
website: https://example.com
careers_url: https://example.com/careers
region: worldwide
remote_policy: fully-remote
company_size: medium
technologies:
- javascript
- python
- cloud
---
## Company blurb
Brief description of what the company does and what makes it unique.
## Company size
Approximate size (e.g., "50-100 employees", "500+", etc.)
## Remote status
Detailed description of remote work policy and culture. Be specific:
- Fully remote or hybrid?
- Remote-first culture?
- Timezone requirements?
- Office visit requirements?
## Region
Geographic regions where the company hires from.
## Company technologies
Main technologies and tools used.
## Office locations
Physical office locations if any (or "None" if fully remote).
## How to apply
Instructions for applying, including links to careers page.
```
### Valid Frontmatter Values
**region** (required):
- `worldwide` - Hires globally
- `americas` - North/South America
- `europe` - Europe
- `americas-europe` - Americas and Europe
- `asia-pacific` - Asia Pacific region
- `other` - Other regions
**remote_policy** (required):
- `fully-remote` - 100% remote, no office required
- `remote-first` - Remote is the default, offices optional
- `hybrid` - Mix of remote and office
- `remote-friendly` - Office-based with remote options
**company_size** (required):
- `tiny` - 1-10 employees
- `small` - 11-50 employees
- `medium` - 51-200 employees
- `large` - 201-1000 employees
- `enterprise` - 1000+ employees
**technologies** (optional array):
- `javascript`, `python`, `ruby`, `go`, `java`, `php`, `rust`, `dotnet`, `elixir`, `scala`
- `cloud`, `devops`, `mobile`, `data`, `ml`, `sql`, `nosql`, `search`
**careers_url** (optional):
- Direct link to the company's careers/jobs page
- If provided, this is where the "Apply Now" button links
- If omitted, falls back to the main `website` URL
**addedAt / updatedAt** (managed by maintainers):
- These date fields are added by project maintainers — do not include them in your PR
- `addedAt` records when the company was first added to the project
- `updatedAt` records when the profile content was last meaningfully changed
## Testing Locally
```bash
# Install dependencies
npm install
# Run the development server (with hot reload)
npm run start
# Visit http://localhost:8080
# Or just build to check for errors
npm run build:11ty
```
## Content Guidelines
### Required Markdown Sections
1. **Company blurb** - What the company does
2. **Company size** - Approximate employee count
3. **Remote status** - Remote work policy and culture (be detailed!)
4. **Region** - Where the company hires from
5. **Company technologies** - Main tech stack
6. **Office locations** - Physical offices (if any)
7. **How to apply** - Application process and links
### Content Quality Standards
- No placeholder text (TODO, FIXME, etc.)
- Complete sentences and proper grammar
- Working links and email addresses
- Clear, helpful information for job seekers
- Be honest about remote work reality (not just marketing copy)
### File Naming
- Use lowercase with hyphens: `awesome-company.md`
- Match company name: "Awesome Company, Inc." → `awesome-company.md`
- The `slug` in frontmatter should match the filename (without `.md`)
## Example
**File**: `src/companies/acme-corp.md`
```markdown
---
title: "Acme Corp"
slug: acme-corp
website: https://acme-corp.com
careers_url: https://acme-corp.com/careers
region: americas-europe
remote_policy: fully-remote
company_size: medium
technologies:
- go
- python
- cloud
- sql
---
## Company blurb
Acme Corp builds cloud-native solutions for enterprise data management. We help companies migrate legacy systems to modern architectures with minimal downtime.
## Company size
150-200 employees
## Remote status
Fully distributed company since 2018. All roles are remote-first with optional coworking stipends. We operate on async-first principles with 4-hour overlap in EST timezone. No mandatory office visits.
## Region
North America and Europe (must be able to work within UTC-8 to UTC+2 timezones)
## Company technologies
Go, Kubernetes, PostgreSQL, React, TypeScript, AWS, Terraform
## Office locations
Small office in San Francisco for those who prefer in-person work (optional)
## How to apply
Visit our careers page at https://acme-corp.com/careers or email jobs@acme-corp.com with your resume and GitHub profile.
```
## Tips for Success
1. **Be specific about remote work** - "Remote-friendly" can mean many things. Explain timezone requirements, in-person expectations, and remote culture maturity.
2. **Keep it current** - Only add companies that are actively hiring remotely.
3. **Be honest** - Don't oversell remote culture. Job seekers appreciate honesty.
4. **Include details** - More detail about technologies and remote work = more helpful.
5. **Proofread** - Check spelling, grammar, and links before submitting.
## Common Mistakes to Avoid
- Using invalid values for `region`, `remote_policy`, or `company_size`
- Missing required frontmatter fields
- Incomplete remote status section (most important!)
- Broken or invalid URLs
- `slug` not matching the filename
- Copy-pasted marketing language without real info
## Need Help?
- Check existing company profiles in `src/companies/` for examples
- Look at recently merged PRs to see what good submissions look like
- Ask questions in your PR if you need clarification
- Review [CLAUDE.md](../CLAUDE.md) for detailed project documentation
## License Note
By contributing, you agree that your contributions will be licensed under the CC0 1.0 Universal license (public domain dedication). See [LICENSE](../LICENSE) for details.
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: dougaitken
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: "[Bug]"
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Feature Requests & Ideas
url: https://github.com/remoteintech/remote-jobs/discussions/new?category=ideas
about: Suggest new features or improvements via GitHub Discussions
- name: Questions & Help
url: https://github.com/remoteintech/remote-jobs/discussions/new?category=q-a
about: Ask questions or get help with contributions
================================================
FILE: .github/ISSUE_TEMPLATE/new_company.md
================================================
---
name: New Company Request
about: Request a remote-friendly company to be added to the directory
title: "[Company] "
labels: new company
assignees: ''
---
## Company Information
**Company Name:**
**Website:**
**Careers Page URL (if different):**
## Remote Work Details
**Remote Policy:** (select one)
- [ ] Fully Remote - 100% remote, no office required
- [ ] Remote First - Remote is the default, offices optional
- [ ] Hybrid - Mix of remote and office
- [ ] Remote Friendly - Office-based with remote options
**Hiring Region:** (select one)
- [ ] Worldwide
- [ ] Americas
- [ ] Europe
- [ ] Americas & Europe
- [ ] Asia Pacific
- [ ] Other (please specify):
**Company Size:**
- [ ] Tiny (1-10 employees)
- [ ] Small (11-50 employees)
- [ ] Medium (51-200 employees)
- [ ] Large (201-1000 employees)
- [ ] Enterprise (1000+ employees)
## Company Description
<!-- Brief description of what the company does -->
## Remote Culture
<!-- Describe the company's remote work policy and culture. Be specific about timezone requirements, in-person expectations, etc. -->
## Technologies Used
<!-- List main technologies, programming languages, and tools used -->
## How to Apply
<!-- Link to careers page or application instructions -->
## Verification
- [ ] I am an employee of this company or can verify this information
- [ ] This company directly hires employees (not a bootcamp/freelance platform)
- [ ] This company genuinely offers remote work opportunities
## Additional Notes
<!-- Any other relevant information -->
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.MD
================================================
## Description
<!-- Briefly describe your changes -->
## Type of Change
- [ ] New company addition
- [ ] Company information update
- [ ] Bug fix
- [ ] Documentation update
- [ ] Other (please describe)
## Checklist
### General Requirements
- [ ] I have read the [Contributing Guidelines](https://github.com/remoteintech/remote-jobs/blob/main/.github/CONTRIBUTING.md)
- [ ] This pull request adheres to the repository's Code of Conduct
- [ ] I have tested the build locally with `npm run build:11ty`
### For Company Additions/Updates
- [ ] Company file is in `src/companies/` with correct filename (lowercase, hyphenated)
- [ ] The company directly hires employees (no bootcamps/freelance platforms)
- [ ] The company offers genuine remote work opportunities
#### Frontmatter Requirements
- [ ] `title` - Company name in quotes
- [ ] `slug` - Matches filename (without .md)
- [ ] `website` - Valid company URL
- [ ] `region` - One of: `worldwide`, `americas`, `europe`, `americas-europe`, `asia-pacific`, `other`
- [ ] `remote_policy` - One of: `fully-remote`, `remote-first`, `hybrid`, `remote-friendly`
- [ ] `company_size` - One of: `tiny`, `small`, `medium`, `large`, `enterprise`
- [ ] `technologies` - Array of valid tech tags (optional)
- [ ] `careers_url` - Direct link to careers page (optional)
- **Note:** `addedAt` and `updatedAt` dates are managed by maintainers — do not include them
#### Content Requirements
- [ ] "Company blurb" section describes what the company does
- [ ] "Remote status" section details the remote work policy and culture
- [ ] "How to apply" section includes application instructions
## Additional Information
<!-- Any other context about your changes -->
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
allow:
- dependency-type: direct
- dependency-type: indirect
================================================
FILE: .github/scripts/validate-companies.js
================================================
#!/usr/bin/env node
/**
* Validates company profile files for correct frontmatter and content structure.
*
* Usage: node validate-companies.js <file1.md> [file2.md ...]
*
* Outputs JSON results to stdout. Exit code 1 if any validation errors found.
*/
import { readFileSync } from "node:fs";
import { basename } from "node:path";
// Canonical enum values (from src/_data/companyHelpers.js)
const VALID_REGIONS = [
"worldwide",
"americas",
"europe",
"americas-europe",
"asia-pacific",
"other",
];
const VALID_REMOTE_POLICIES = [
"fully-remote",
"remote-first",
"hybrid",
"remote-friendly",
];
const VALID_COMPANY_SIZES = ["tiny", "small", "medium", "large", "enterprise"];
const VALID_TECHNOLOGIES = [
"javascript",
"python",
"ruby",
"go",
"java",
"php",
"rust",
"dotnet",
"elixir",
"scala",
"cloud",
"devops",
"mobile",
"data",
"ml",
"sql",
"nosql",
"search",
];
const REQUIRED_FIELDS = [
"title",
"slug",
"website",
"region",
"remote_policy",
"company_size",
];
const REQUIRED_SECTIONS = ["Company blurb", "Remote status", "How to apply"];
/**
* Parse YAML frontmatter from a markdown file's content.
* Returns null if no frontmatter block is found.
*/
function parseFrontmatter(content) {
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!match) return null;
const yaml = match[1];
const data = {};
let currentArrayKey = null;
for (const line of yaml.split("\n")) {
// Array item (indented "- value")
const arrayItem = line.match(/^\s+-\s+(.+)$/);
if (arrayItem && currentArrayKey) {
data[currentArrayKey].push(arrayItem[1].trim());
continue;
}
// Key-value pair
const kv = line.match(/^(\w[\w_]*):\s*(.*)$/);
if (kv) {
const key = kv[1];
let value = kv[2].trim();
// Check if this starts an array (empty value followed by "- items")
if (value === "" || value === "[]") {
data[key] = [];
currentArrayKey = key;
continue;
}
// Strip surrounding quotes
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
data[key] = value;
currentArrayKey = null;
}
}
return data;
}
/**
* Validate a single company file. Returns an array of error strings.
*/
function validateCompanyFile(filePath) {
const errors = [];
const warnings = [];
let content;
try {
content = readFileSync(filePath, "utf-8");
} catch (err) {
errors.push(`Could not read file: ${err.message}`);
return { errors, warnings };
}
// Parse frontmatter
const data = parseFrontmatter(content);
if (!data) {
errors.push(
"Missing YAML frontmatter. The file must start with `---` followed by YAML fields and a closing `---`."
);
return { errors, warnings };
}
// Check required fields
for (const field of REQUIRED_FIELDS) {
if (!data[field] || (typeof data[field] === "string" && !data[field].trim())) {
errors.push(`Missing required field: \`${field}\``);
}
}
// Validate enum values (only if field exists)
if (data.region && !VALID_REGIONS.includes(data.region)) {
errors.push(
`Invalid \`region\`: "${data.region}". Must be one of: ${VALID_REGIONS.join(", ")}`
);
}
if (data.remote_policy && !VALID_REMOTE_POLICIES.includes(data.remote_policy)) {
errors.push(
`Invalid \`remote_policy\`: "${data.remote_policy}". Must be one of: ${VALID_REMOTE_POLICIES.join(", ")}`
);
}
if (data.company_size && !VALID_COMPANY_SIZES.includes(data.company_size)) {
errors.push(
`Invalid \`company_size\`: "${data.company_size}". Must be one of: ${VALID_COMPANY_SIZES.join(", ")}`
);
}
// Validate technologies array
if (data.technologies && Array.isArray(data.technologies)) {
for (const tech of data.technologies) {
if (!VALID_TECHNOLOGIES.includes(tech)) {
errors.push(
`Invalid technology tag: "${tech}". Valid tags: ${VALID_TECHNOLOGIES.join(", ")}`
);
}
}
}
// Validate slug matches filename
if (data.slug) {
const expectedSlug = basename(filePath, ".md");
if (data.slug !== expectedSlug) {
errors.push(
`Slug "${data.slug}" does not match filename "${basename(filePath)}". The slug must be "${expectedSlug}".`
);
}
}
// Validate URL format
if (data.website && !isValidUrl(data.website)) {
errors.push(
`Invalid \`website\` URL: "${data.website}". Must be a full URL starting with https:// or http://`
);
}
if (data.careers_url && !isValidUrl(data.careers_url)) {
errors.push(
`Invalid \`careers_url\`: "${data.careers_url}". Must be a full URL starting with https:// or http://`
);
}
// Check required markdown sections
const bodyContent = content.replace(/^---[\s\S]*?---/, "");
for (const section of REQUIRED_SECTIONS) {
const sectionPattern = new RegExp(
`^##\\s+${escapeRegExp(section)}\\s*$`,
"m"
);
if (!sectionPattern.test(bodyContent)) {
errors.push(`Missing required section: "## ${section}"`);
}
}
// Check that "Company blurb" has content (not just the heading)
const blurbMatch = bodyContent.match(
/^##\s+Company blurb\s*\n([\s\S]*?)(?=^##\s|\s*$)/m
);
if (blurbMatch && !blurbMatch[1].trim()) {
warnings.push(
'The "Company blurb" section is empty. Please add a description of the company.'
);
}
return { errors, warnings };
}
function isValidUrl(str) {
try {
const url = new URL(str);
return url.protocol === "https:" || url.protocol === "http:";
} catch {
return false;
}
}
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
// --- Main ---
const args = process.argv.slice(2);
if (args.length === 0) {
console.error("Usage: node validate-companies.js <file1.md> [file2.md ...]");
process.exit(2);
}
// Separate company files from old-format files
const companyFiles = [];
const oldFormatFiles = [];
for (const file of args) {
// Strip pr-head/ prefix used in pull_request_target checkout
const normalized = file.replace(/^pr-head\//, "");
if (normalized.startsWith("company-profiles/")) {
oldFormatFiles.push(normalized);
} else if (normalized.startsWith("src/companies/") && normalized.endsWith(".md")) {
// Store both the actual path (for reading) and the display path (for output)
companyFiles.push({ actual: file, display: normalized });
}
}
const results = {
oldFormatFiles,
files: {},
summary: { total: 0, passed: 0, failed: 0, warnings: 0 },
};
for (const { actual, display } of companyFiles) {
const { errors, warnings } = validateCompanyFile(actual);
results.files[display] = { errors, warnings };
results.summary.total++;
if (errors.length > 0) {
results.summary.failed++;
} else {
results.summary.passed++;
}
if (warnings.length > 0) {
results.summary.warnings++;
}
}
console.log(JSON.stringify(results, null, 2));
const hasErrors =
results.summary.failed > 0 || results.oldFormatFiles.length > 0;
process.exit(hasErrors ? 1 : 0);
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build site
run: npm run build
env:
NODE_OPTIONS: '--max-old-space-size=8192'
- name: Verify build output
run: |
echo "Build completed successfully"
echo "Pages generated: $(find dist -name '*.html' | wc -l)"
================================================
FILE: .github/workflows/codeql-analysis.yml
================================================
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [main]
pull_request:
# The branches below must be a subset of the branches above
branches: [main]
schedule:
- cron: '0 6 * * 5'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: ['javascript']
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v3
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
================================================
FILE: .github/workflows/validate-companies.yml
================================================
name: Validate Company Profiles
on:
pull_request_target:
branches: [main]
paths:
- 'src/companies/**'
- 'company-profiles/**'
permissions:
contents: read
pull-requests: write
jobs:
validate:
# Skip validation for repo owner and bots
if: >-
github.event.pull_request.user.login != 'dougaitken' &&
github.event.pull_request.user.type != 'Bot' &&
github.event.pull_request.author_association != 'OWNER' &&
github.event.pull_request.author_association != 'MEMBER'
runs-on: ubuntu-latest
steps:
- name: Checkout base (for workflow scripts)
uses: actions/checkout@v4
- name: Checkout PR company files
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
path: pr-head
sparse-checkout: |
src/companies
company-profiles
- name: Get changed files
id: changed
run: |
FILES=$(gh pr diff ${{ github.event.pull_request.number }} --name-only | grep -E '(^src/companies/.*\.md$|^company-profiles/)' || true)
echo "files<<EOF" >> "$GITHUB_OUTPUT"
echo "$FILES" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run validation
id: validate
if: steps.changed.outputs.files != ''
run: |
set +e
# Remap file paths to the PR checkout directory
MAPPED_FILES=""
for f in ${{ steps.changed.outputs.files }}; do
MAPPED_FILES="$MAPPED_FILES pr-head/$f"
done
RESULT=$(node .github/scripts/validate-companies.js $MAPPED_FILES)
EXIT_CODE=$?
set -e
echo "json<<EOF" >> "$GITHUB_OUTPUT"
echo "$RESULT" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
echo "exit_code=$EXIT_CODE" >> "$GITHUB_OUTPUT"
- name: Post PR comment
if: steps.changed.outputs.files != ''
uses: actions/github-script@v7
with:
script: |
const result = JSON.parse(process.env.VALIDATION_JSON);
const exitCode = parseInt(process.env.EXIT_CODE, 10);
let body = '';
// Old-format detection
if (result.oldFormatFiles && result.oldFormatFiles.length > 0) {
body += '### Thanks for your contribution!\n\n';
body += 'It looks like your PR uses an older file format. We\'ve updated how company profiles work. ';
body += 'Please create a new file in `src/companies/` instead.\n\n';
body += '**Files using the old format:**\n';
for (const f of result.oldFormatFiles) {
body += `- \`${f}\`\n`;
}
body += '\n<details>\n<summary>New format template</summary>\n\n';
body += '```markdown\n';
body += '---\n';
body += 'title: "Your Company Name"\n';
body += 'slug: your-company-slug\n';
body += 'website: https://yourcompany.com\n';
body += 'careers_url: https://yourcompany.com/careers\n';
body += 'region: worldwide\n';
body += 'remote_policy: fully-remote\n';
body += 'company_size: small\n';
body += 'technologies:\n';
body += ' - javascript\n';
body += ' - python\n';
body += '---\n\n';
body += '> **Note:** `addedAt` and `updatedAt` dates are managed by maintainers — do not include them in your PR.\n\n';
body += '## Company blurb\n\n';
body += 'A short description of your company.\n\n';
body += '## Remote status\n\n';
body += 'Describe your remote work culture.\n\n';
body += '## How to apply\n\n';
body += 'Link to your careers page or application instructions.\n';
body += '```\n\n';
body += '</details>\n\n';
body += 'The filename should be `src/companies/{slug}.md` where `{slug}` matches the `slug` field in the frontmatter.\n\n';
}
// Validation results for new-format files
if (result.summary && result.summary.total > 0) {
if (result.summary.failed === 0 && result.oldFormatFiles.length === 0) {
body += '### Company profile validation passed!\n\n';
body += `All ${result.summary.total} company file(s) look good. Thanks for following the format!\n`;
if (result.summary.warnings > 0) {
body += '\n**Warnings:**\n';
for (const [file, res] of Object.entries(result.files)) {
for (const w of res.warnings) {
body += `- \`${file}\`: ${w}\n`;
}
}
}
} else if (result.summary.failed > 0) {
if (!body) body += '### Thanks for your contribution!\n\n';
body += 'The following issues were found with your company profile(s). ';
body += 'Please fix them and push an update to this PR:\n\n';
for (const [file, res] of Object.entries(result.files)) {
if (res.errors.length === 0 && res.warnings.length === 0) continue;
body += `**\`${file}\`**\n`;
for (const e of res.errors) {
body += `- :x: ${e}\n`;
}
for (const w of res.warnings) {
body += `- :warning: ${w}\n`;
}
body += '\n';
}
body += 'See the [company profile format docs](https://github.com/remoteintech/remote-jobs/blob/main/CLAUDE.md#company-profiles) for reference.\n';
}
}
if (!body) return;
// Find and update existing bot comment, or create new one
const marker = '<!-- validate-companies -->';
body = marker + '\n' + body;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
env:
VALIDATION_JSON: ${{ steps.validate.outputs.json }}
EXIT_CODE: ${{ steps.validate.outputs.exit_code }}
- name: Fail if validation errors
if: steps.validate.outputs.exit_code == '1'
run: |
echo "Validation failed. See the PR comment for details."
exit 1
================================================
FILE: .gitignore
================================================
# Node modules
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# generated files
dist
src/_includes/css
src/_includes/scripts
# cache
.cache
# secret data
.env
.env.*
!.env.example
# OS files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Editor files
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.idea
*.swp
*.swo
*~
# Test files
coverage
.nyc_output
# Logs
logs
*.log
# Temporary files
tmp
temp
*.tmp
# Package manager
package-lock.json.bak
yarn.lock.bak
# Local tools and working files
_tools
backup-companies-old
# Link check reports
extracted-urls.txt
link-check-results.csv
link-check-results.csv.bak
check-links.sh
link-report.html
================================================
FILE: .nojekyll
================================================
================================================
FILE: .nvmrc
================================================
22
================================================
FILE: .prettierignore
================================================
src/**/*.md
src/_includes/components/**/custom-*.njk
src/common/*
src/_includes/scripts/*
================================================
FILE: .prettierrc
================================================
{
"printWidth": 110,
"tabWidth": 2,
"singleQuote": true,
"bracketSpacing": false,
"quoteProps": "consistent",
"trailingComma": "none",
"arrowParens": "avoid",
"plugins": ["prettier-plugin-jinja-template"],
"overrides": [
{
"files": "*.njk",
"options": {
"parser": "jinja-template"
}
}
]
}
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
## Shorthand
- **"Punch it"** = commit, push, and open a PR to upstream, all in one go.
## Project Overview
Remote In Tech is a community-maintained directory of remote-friendly tech companies. The site is built with [Eleventy](https://www.11ty.dev/) (v3) and deployed on Netlify.
**Live site:** https://remoteintech.company
## Directory Structure
```
src/
├── companies/ # Company profiles (Markdown with YAML frontmatter)
├── blog/ # Blog posts (Markdown)
├── pages/ # Static pages and dynamic templates (Nunjucks)
├── common/ # Shared templates (404, redirects, sitemap, feeds)
├── _layouts/ # Page layouts (base, page, post, company)
├── _includes/ # Partials, components, and WebC components
├── _config/ # Eleventy configuration (collections, filters, plugins)
├── _data/ # Global data files (navigation, meta, companyTags)
└── assets/ # Static assets (CSS, JS, fonts, images)
dist/ # Build output (gitignored)
```
## Build Commands
```bash
npm run start # Development server with hot reload
npm run build # Production build (clean + eleventy + pagefind)
npm run build:11ty # Eleventy build only (faster for testing)
npm run clean # Remove dist and generated files
```
## Company Profiles
Companies are Markdown files in `src/companies/` with this frontmatter structure:
```yaml
---
title: "Company Name"
slug: company-slug # URL slug (required)
website: https://example.com # Main company website URL
careers_url: https://example.com/jobs # Optional: careers/jobs page URL
region: worldwide # worldwide, americas, europe, asia-pacific, americas-europe, other
remote_policy: fully-remote # fully-remote, remote-first, remote-friendly, hybrid
company_size: startup # startup, small, medium, large, enterprise
technologies: # Array of tech tags
- javascript
- python
- devops
addedAt: 2024-03-15 # Date first contributed (from git history)
updatedAt: 2025-06-20 # Date of last real content change (from git history)
---
```
**URL fields:**
- `website` - Main company URL (used to verify the company, identify brand)
- `careers_url` - Optional careers/jobs page URL. When present, the "Apply Now" button links here; otherwise falls back to `website`
Valid technology tags are defined in `src/_data/companyHelpers.js` under `techLabels`.
## Key Configuration Files
- `eleventy.config.js` - Main Eleventy config (imports from src/_config/)
- `src/_config/collections.js` - Company and blog collections
- `src/_data/companyHelpers.js` - Tech labels, region labels, featured companies
- `src/_data/companyTags.js` - Generates browse page tags with counts
- `src/_data/meta.js` - Site metadata (title, description, analytics)
- `src/common/_redirects.njk` - Netlify redirects (auto-generates company redirects)
## Coding Conventions
- **Templates:** Nunjucks (.njk) for layouts and pages
- **Content:** Markdown with YAML frontmatter for companies and blog posts
- **Styles:** CUBE CSS methodology with Tailwind as a design token processor (not traditional utility classes)
- **Components:** WebC components in `src/_includes/webc/`
- **Local CSS:** Use `{%- css "local" -%}` blocks for page-specific styles
- **Design Tokens:** Defined in `src/_data/designTokens/` (colors, spacing, typography)
## Collections
Access these in templates:
- `collections.companies` - All companies (alphabetically sorted)
- `collections.featuredCompanies` - Randomly shuffled subset (8 of 12) from curated list
- `collections.recentCompanies` - Recently added (by addedAt date)
- `collections.companiesByRegion` - Grouped by region
- `collections.companiesByTech` - Grouped by technology
- `collections.allPosts` - Blog posts (reverse chronological)
## Search
Site search is powered by [Pagefind](https://pagefind.app/):
- **Nav search:** Dropdown search in the navigation bar (quick results)
- **Search page:** `/search/` - Full search results page with pagination
- Search index is built during `npm run build` via the pagefind post-process step
## Redirects
Legacy URL redirects are auto-generated in `src/common/_redirects.njk`:
- Old company URLs (`/company-slug`) redirect to new format (`/companies/company-slug/`)
- Blog subdomain redirects from old WordPress site
## Analytics
Fathom Analytics (privacy-focused) - only loads in production builds.
- Site ID configured in `src/_data/meta.js`
- 404 errors tracked via custom event with the attempted URL path
## SEO
### AI Bot Policy
`src/common/robots.njk` generates robots.txt with a dual policy:
- **Allowed:** AI search bots (ChatGPT-User, Claude-User, PerplexityBot, YouBot, Applebot-Extended)
- **Blocked:** AI training crawlers (GPTBot, CCBot, ClaudeBot, Google-Extended, FacebookBot, anthropic-ai, cohere-ai)
`AGENTS.md` is a symlink to `CLAUDE.md` for broader AI agent compatibility.
### Structured Data (JSON-LD)
Schema.org markup in `src/_includes/schemas/`:
- `WebSite.njk` - Site info with SearchAction for sitelinks search box
- `BreadcrumbList.njk` - Auto-generated breadcrumb trail from URL path
- `Organization.njk` - Company profile structured data
- `BlogPosting.njk` - Blog post structured data
Schemas are included via `src/_includes/head/schema.njk`. Page-specific schemas use the `schema` frontmatter field.
### Meta Descriptions
Company pages auto-generate meta descriptions from the "Company blurb" section via computed data in `src/companies/companies.11tydata.js`. Descriptions are truncated to ~155 characters at sentence boundaries.
### Company Dates
Company profiles have `addedAt` and `updatedAt` dates stored directly in frontmatter:
- **`addedAt`** - Date when the company was first contributed to the project (traced through git history, including the pre-migration `company-profiles/` path)
- **`updatedAt`** - Date of the last real content change (excludes bulk migrations and infrastructure commits). Some companies only have `addedAt` if no genuine content update occurred after the initial contribution.
These dates were backfilled from a decade of git history using `git log --follow` to trace files through the October 2025 migration from `company-profiles/` to `src/companies/`. They are now static frontmatter values — no git commands run during build.
The homepage "Recently Added" section uses `addedAt` to show the most recently added companies. Company profile pages display "Last updated: [date]" in the footer.
### Social Cards
Twitter/X card meta tags are included in `src/_includes/head/meta-info.njk`:
- `twitter:card` - summary_large_image
- `twitter:title`, `twitter:description`, `twitter:image`
Open Graph tags are also present for Facebook/LinkedIn sharing.
## Deployment
- **Platform:** Netlify (auto-deploys from main branch)
- **Build command:** `npm run build`
- **Publish directory:** `dist`
- **Node version:** 22 (specified in package.json engines)
## Versioning
The site uses semantic versioning in `package.json`. The version displays in the footer and links to `/changelog/`.
- **Major version bump**: Reserved for full site eras/redesigns (v1 = flat list, v2 = linked profiles, v3 = original live site, v4 = Eleventy rebuild)
- **Minor version bump** (e.g. 4.3.0 → 4.4.0): Visible changes — new companies, blog posts, feature additions/removals, data changes visitors can see
- **Patch version bump** (e.g. 4.4.0 → 4.4.1): Invisible changes — bug fixes, refactoring, documentation, CI/CD, performance improvements
When bumping the version:
1. Update `version` in `package.json`
2. Add a new entry at the top of `src/_data/changelog.json`
Changelog entries should be summaries. Call out each company addition by name, but summarise edits, deletions, and fixes. Change types: `added`, `changed`, `fixed`, `removed`.
## Contributing a Company
1. Create `src/companies/{slug}.md` with required frontmatter
2. Add company description in Markdown body
3. Run `npm run build` to verify it builds correctly
4. Submit PR to `remoteintech/remote-jobs`
## Processing Contributor PRs
PRs that touch company files are automatically validated by the **Validate Company Profiles** Action (`.github/workflows/validate-companies.yml`). The bot posts a comment with specific feedback and blocks merging until issues are fixed.
**Workflow:**
1. **Check the automated validation comment** on the PR for any issues
2. **If the contributor needs more guidance** beyond what the bot provided, leave a helpful comment explaining what to fix
3. **For old-format PRs** (files in `company-profiles/` or changes to `README.md`), the bot will explain the new format with a template — ask the contributor to update their PR
4. **Only re-create a PR yourself as a last resort** if the contributor is unresponsive after reasonable follow-up
**Reject PRs that:**
- Promote harmful services (hacking tools, spam, etc.)
- Have minimal or no meaningful content
- Are duplicates of existing companies
## GitHub Actions
- **CI** (`.github/workflows/ci.yml`): Runs on push/PR to main. Builds the site with Node 22.
- **Validate Company Profiles** (`.github/workflows/validate-companies.yml`): Runs on PRs that touch company files. Validates frontmatter, enum values, slug/filename match, URL format, and required sections. Posts a PR comment with results and blocks merge on errors.
- **CodeQL** (`.github/workflows/codeql-analysis.yml`): Security scanning on push/PR and weekly schedule.
## Branch Protection
The `main` branch has protection rules:
- Requires pull request reviews (1 approval)
- Requires `build` status check to pass
- Only repository owner can push directly
- Force pushes and deletions disabled
- Admins can bypass when needed
## Link Checker
A script to check all outbound URLs in company profiles for broken links and redirects.
### Files (gitignored)
- `check-links.sh` - Link checker script
- `extracted-urls.txt` - List of all URLs extracted from company files
- `link-check-results.csv` - Results with status codes and explanations
- `link-report.html` - Interactive HTML report for viewing results
### Commands
```bash
# Full check - all URLs (~8 min for ~2,200 URLs)
./check-links.sh
# Quick check - only re-check URLs that weren't OK last time
./check-links.sh --quick
# Refresh - re-extract URLs from company files, then full check
./check-links.sh --refresh
```
### Viewing Results
Open `link-report.html` directly in a browser and load `link-check-results.csv` via the file picker (or drag and drop).
### CSV Columns
| Column | Description |
|--------|-------------|
| `source_file` | Company profile containing the link |
| `original_url` | URL as it appears in the file |
| `resolved_url` | Final URL after redirects |
| `status_code` | HTTP status (200, 404, ERROR, etc.) |
| `explanation` | What happened (OK, Redirects, Not found, etc.) |
| `no_change_needed` | Yes / No / Review |
### Workflow
1. Run `./check-links.sh` for initial full scan
2. Fix broken links in company profiles
3. Run `./check-links.sh --quick` to verify fixes
4. Repeat until satisfied
================================================
FILE: LICENSE
================================================
## Licenses
This project is licensed under the ISC License. Additionally, it includes components that are licensed under the MIT and SIL License.
### ISC License
Copyright (c) 2024 Lene Saile
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
### MIT License
The **Cube Boilerplate** is licensed under the MIT License:
Copyright (c) 2024 Set Studio
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
### SIL OPEN FONT LICENSE
The Fonts **Red Hat Display** and **Atkinson Hyperlegible** are licensed under the SIL License, Version 1.1.
Copyright (c) Red Hat, Inc. and (c) Braille Institute.
This license allows
- Commercial use
- Modifying
- Redistribution
- Full license: https://openfontlicense.org/
================================================
FILE: README.md
================================================
# Remote In Tech
**[remoteintech.company](https://remoteintech.company)** — A community-maintained directory of remote-friendly tech companies.
> This repository is the source code for the site. To browse the directory, visit **[remoteintech.company](https://remoteintech.company)**.
## Contributing a Company
We welcome contributions! To add a company to the directory:
1. Create a file at `src/companies/{company-slug}.md`
2. Use the frontmatter template below
3. Add a company description in the Markdown body
4. Run `npm run build` to verify it builds
5. Submit a PR to this repo
### Company Profile Template
```yaml
---
title: "Company Name"
slug: company-slug
website: https://example.com
careers_url: https://example.com/careers
region: worldwide # worldwide, americas, europe, asia-pacific, americas-europe, other
remote_policy: fully-remote # fully-remote, remote-first, remote-friendly, hybrid
company_size: small # startup, small, medium, large, enterprise
technologies:
- javascript
- python
---
## Company blurb
A short description of the company and what they do.
```
PRs are automatically validated — the bot will comment with any issues to fix.
## Development
```bash
npm install # Install dependencies
npm run start # Dev server with hot reload
npm run build # Production build
```
Requires Node.js 22+.
## Credits
Built with [Eleventy Excellent](https://github.com/madrilene/eleventy-excellent) by [Lene Saile](https://github.com/madrilene).
## License
ISC
================================================
FILE: eleventy.config.js
================================================
/**
* Most adjustments must be made in `./src/_config/*`
*
* Hint VS Code for eleventyConfig autocompletion.
* © Henry Desroches - https://gist.github.com/xdesro/69583b25d281d055cd12b144381123bf
* @param {import("@11ty/eleventy/src/UserConfig")} eleventyConfig -
* @returns {Object} -
*/
// register dotenv for process.env.* variables to pickup
import dotenv from 'dotenv';
dotenv.config();
// add yaml support
import yaml from 'js-yaml';
// config import
import {
getAllPosts,
getAllCompanies,
getFeaturedCompanies,
getRecentCompanies,
getCompaniesByRegion,
getCompaniesByTech,
getCompanyTags,
showInSitemap,
tagList
} from './src/_config/collections.js';
import events from './src/_config/events.js';
import filters from './src/_config/filters.js';
import plugins from './src/_config/plugins.js';
import shortcodes from './src/_config/shortcodes.js';
export default async function (eleventyConfig) {
// --------------------- Events: before build
eleventyConfig.on('eleventy.before', async () => {
await events.buildAllCss();
await events.buildAllJs();
});
// --------------------- custom wtach targets
eleventyConfig.addWatchTarget('./src/assets/**/*.{css,js,svg,png,jpeg}');
eleventyConfig.addWatchTarget('./src/_includes/**/*.{webc}');
// --------------------- layout aliases
eleventyConfig.addLayoutAlias('base', 'base.njk');
eleventyConfig.addLayoutAlias('page', 'page.njk');
eleventyConfig.addLayoutAlias('post', 'post.njk');
eleventyConfig.addLayoutAlias('company', 'company.njk');
eleventyConfig.addLayoutAlias('tags', 'tags.njk');
// --------------------- Collections
eleventyConfig.addCollection('allPosts', getAllPosts);
eleventyConfig.addCollection('companies', getAllCompanies);
eleventyConfig.addCollection('featuredCompanies', getFeaturedCompanies);
eleventyConfig.addCollection('recentCompanies', getRecentCompanies);
eleventyConfig.addCollection('companiesByRegion', getCompaniesByRegion);
eleventyConfig.addCollection('companiesByTech', getCompaniesByTech);
eleventyConfig.addCollection('companyTags', getCompanyTags);
eleventyConfig.addCollection('showInSitemap', showInSitemap);
eleventyConfig.addCollection('tagList', tagList);
// --------------------- Plugins
eleventyConfig.addPlugin(plugins.htmlConfig);
eleventyConfig.addPlugin(plugins.drafts);
eleventyConfig.addPlugin(plugins.EleventyRenderPlugin);
eleventyConfig.addPlugin(plugins.rss);
eleventyConfig.addPlugin(plugins.syntaxHighlight);
eleventyConfig.addPlugin(plugins.webc, {
components: ['./src/_includes/webc/**/*.webc'],
useTransform: true
});
eleventyConfig.addPlugin(plugins.eleventyImageTransformPlugin, {
formats: ['webp', 'jpeg'],
widths: ['auto'],
htmlOptions: {
imgAttributes: {
loading: 'lazy',
decoding: 'async',
sizes: 'auto'
},
pictureAttributes: {}
}
});
// --------------------- bundle
eleventyConfig.addBundle('css', {hoist: true});
// --------------------- Library and Data
eleventyConfig.setLibrary('md', plugins.markdownLib);
eleventyConfig.addDataExtension('yaml', contents => yaml.load(contents));
// --------------------- Filters
eleventyConfig.addFilter('toIsoString', filters.toISOString);
eleventyConfig.addFilter('formatDate', filters.formatDate);
eleventyConfig.addFilter('markdownFormat', filters.markdownFormat);
eleventyConfig.addFilter('splitlines', filters.splitlines);
eleventyConfig.addFilter('striptags', filters.striptags);
eleventyConfig.addFilter('alphabetic', filters.sortAlphabetically);
eleventyConfig.addFilter('slugify', filters.slugifyString);
eleventyConfig.addFilter('fileExists', filters.fileExists);
eleventyConfig.addFilter('split', filters.split);
// --------------------- Shortcodes
eleventyConfig.addShortcode('svg', shortcodes.svgShortcode);
eleventyConfig.addShortcode('image', shortcodes.imageShortcode);
eleventyConfig.addShortcode('year', () => `${new Date().getFullYear()}`);
// --------------------- Events: after build
if (process.env.ELEVENTY_RUN_MODE === 'serve') {
eleventyConfig.on('eleventy.after', events.svgToJpeg);
}
// --------------------- Passthrough File Copy
// -- same path
['src/assets/fonts/', 'src/assets/images/template', 'src/assets/og-images', 'src/assets/scripts/'].forEach(path =>
eleventyConfig.addPassthroughCopy(path)
);
eleventyConfig.addPassthroughCopy({
// -- to root
'src/assets/images/favicon/*': '/',
// -- node_modules
'node_modules/lite-youtube-embed/src/lite-yt-embed.{css,js}': `assets/components/`
});
// --------------------- general config
return {
markdownTemplateEngine: 'njk',
dir: {
output: 'dist',
input: 'src',
includes: '_includes',
layouts: '_layouts'
}
};
}
================================================
FILE: package.json
================================================
{
"name": "remote-in-tech",
"version": "4.4.1",
"description": "A list of semi to fully remote-friendly companies in or around tech",
"author": "Doug Aitken",
"license": "ISC",
"type": "module",
"engines": {
"node": ">=22"
},
"scripts": {
"clean": "rimraf dist src/_includes/css src/_includes/scripts",
"clean:og": "rimraf src/assets/og-images",
"favicons": "node ./src/_config/setup/generate-favicons.js",
"colors": "node ./src/_config/setup/create-colors.js",
"dev:11ty": "cross-env ELEVENTY_ENV=development eleventy --serve",
"build:11ty": "cross-env ELEVENTY_ENV=production eleventy",
"pagefind": "npx pagefind --site dist --glob \"**/*.html\"",
"start": "npm run dev:11ty",
"build": "npm run clean && npm run build:11ty && npm run pagefind"
},
"keywords": [],
"repository": {
"type": "git",
"url": "https://github.com/remoteintech/remote-jobs.git"
},
"dependencies": {
"@11ty/eleventy": "^3.1.2",
"@11ty/eleventy-fetch": "^5.1.0",
"@11ty/eleventy-img": "^6.0.4",
"@11ty/eleventy-plugin-rss": "^2.0.4",
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2",
"@11ty/eleventy-plugin-webc": "^0.11.2",
"@11ty/is-land": "^4.0.1",
"lite-youtube-embed": "^0.3.3",
"tailwindcss": "^3.4.17"
},
"overrides": {
"nanoid": "^5.0.9"
},
"devDependencies": {
"@toycode/markdown-it-class": "^1.2.4",
"autoprefixer": "^10.4.24",
"colorjs.io": "^0.5.2",
"cross-env": "^10.1.0",
"cssnano": "^7.1.2",
"dayjs": "^1.11.18",
"dotenv": "^17.2.3",
"esbuild": "^0.25.10",
"fast-glob": "^3.3.3",
"html-minifier-terser": "^7.2.0",
"js-yaml": "^4.1.0",
"markdown-it": "^14.1.1",
"markdown-it-abbr": "^2.0.0",
"markdown-it-anchor": "^9.2.0",
"markdown-it-attrs": "^4.3.1",
"markdown-it-emoji": "^3.0.0",
"markdown-it-footnote": "^4.0.0",
"markdown-it-link-attributes": "^4.0.1",
"markdown-it-mark": "^4.0.0",
"markdown-it-prism": "^3.0.0",
"netlify-plugin-cache": "^1.0.3",
"pagefind": "^1.2.0",
"postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"postcss-import": "^16.1.1",
"postcss-import-ext-glob": "^2.1.1",
"postcss-js": "^5.0.3",
"prettier-plugin-jinja-template": "^2.1.0",
"rimraf": "^6.1.2",
"sanitize-html": "^2.17.1",
"sharp": "^0.34.5",
"sharp-ico": "^0.1.5",
"slugify": "^1.6.6",
"svgo": "^4.0.0"
}
}
================================================
FILE: src/_config/collections.js
================================================
import {
featuredCompanySlugs,
regionLabels,
remotePolicyLabels,
techLabels
} from '../_data/companyHelpers.js';
import { shuffleArray } from './filters/sort-random.js';
/** Memoized glob results — avoids filtering ~850 items 6 times */
let _companyCache = null;
const getCompanies = collection => {
if (!_companyCache) {
_companyCache = collection.getFilteredByGlob('./src/companies/**/*.md');
}
return _companyCache;
};
/** All blog posts as a collection. */
export const getAllPosts = collection => {
return collection.getFilteredByGlob('./src/blog/**/*.md').reverse();
};
/** All company profiles as a collection, sorted alphabetically */
export const getAllCompanies = collection => {
return [...getCompanies(collection)].sort((a, b) => {
const nameA = (a.data.title || '').toLowerCase();
const nameB = (b.data.title || '').toLowerCase();
return nameA.localeCompare(nameB);
});
};
/** Featured companies - randomly selected from curated list */
export const getFeaturedCompanies = collection => {
const companies = getCompanies(collection);
const matched = featuredCompanySlugs
.map(slug => companies.find(c => c.data.slug === slug || c.fileSlug === slug))
.filter(Boolean);
return shuffleArray(matched).slice(0, 8);
};
/** Recently added companies (by addedAt date from frontmatter) */
export const getRecentCompanies = collection => {
return [...getCompanies(collection)]
.filter(c => c.data.addedAt)
.sort((a, b) => b.data.addedAt - a.data.addedAt)
.slice(0, 12);
};
/** Companies grouped by region */
export const getCompaniesByRegion = collection => {
const companies = getCompanies(collection);
const regionGroups = {};
// Initialize all regions
Object.keys(regionLabels).forEach(region => {
regionGroups[region] = [];
});
companies.forEach(company => {
const region = company.data.region || 'other';
if (!regionGroups[region]) {
regionGroups[region] = [];
}
regionGroups[region].push(company);
});
return regionGroups;
};
/** Companies grouped by technology */
export const getCompaniesByTech = collection => {
const companies = getCompanies(collection);
const techGroups = {};
// Initialize all technologies
Object.keys(techLabels).forEach(tech => {
techGroups[tech] = [];
});
companies.forEach(company => {
const technologies = company.data.technologies || [];
technologies.forEach(tech => {
if (!techGroups[tech]) {
techGroups[tech] = [];
}
techGroups[tech].push(company);
});
});
return techGroups;
};
/** All relevant pages as a collection for sitemap.xml */
export const showInSitemap = collection => {
return collection.getFilteredByGlob('./src/**/*.{md,njk}');
};
/** All tags from blog posts as a collection - excluding custom collections */
export const tagList = collection => {
const tagsSet = new Set();
// Only get tags from blog posts, not from companies or other content
collection.getFilteredByGlob('./src/blog/**/*.md').forEach(item => {
if (!item.data.tags || !Array.isArray(item.data.tags)) return;
item.data.tags
.filter(tag => typeof tag === 'string' && !['posts', 'docs', 'all'].includes(tag))
.forEach(tag => tagsSet.add(tag));
});
return Array.from(tagsSet).sort();
};
/** Tag type definitions with metadata */
const tagTypes = {
technology: {
labels: techLabels,
plural: 'Technologies',
description: 'Companies using this technology'
},
region: {
labels: regionLabels,
plural: 'Regions',
description: 'Companies hiring in this region'
},
'remote-policy': {
labels: remotePolicyLabels,
plural: 'Remote Policies',
description: 'Companies with this remote work policy'
}
};
/**
* Company tags for browse/tag pages — derived from Eleventy collections
* instead of manual file I/O. Replaces the old companyTags.js data file.
*/
export const getCompanyTags = collection => {
const companies = getCompanies(collection);
const tagData = {
technology: {},
region: {},
'remote-policy': {}
};
for (const company of companies) {
const d = company.data;
const companyInfo = {
title: d.title || company.fileSlug,
slug: d.slug || company.fileSlug,
website: d.website,
region: d.region,
remote_policy: d.remote_policy
};
// Technologies
if (Array.isArray(d.technologies)) {
for (const tech of d.technologies) {
if (!tagData.technology[tech]) tagData.technology[tech] = [];
tagData.technology[tech].push(companyInfo);
}
}
// Region
if (d.region) {
if (!tagData.region[d.region]) tagData.region[d.region] = [];
tagData.region[d.region].push(companyInfo);
}
// Remote policy
if (d.remote_policy) {
if (!tagData['remote-policy'][d.remote_policy]) tagData['remote-policy'][d.remote_policy] = [];
tagData['remote-policy'][d.remote_policy].push(companyInfo);
}
}
// Build final tag list with metadata
const allTags = [];
for (const [type, tags] of Object.entries(tagData)) {
const typeInfo = tagTypes[type];
for (const [slug, tagCompanies] of Object.entries(tags)) {
tagCompanies.sort((a, b) => a.title.localeCompare(b.title));
allTags.push({
slug,
type,
label: typeInfo.labels[slug] || slug,
description: typeInfo.description,
typePlural: typeInfo.plural,
count: tagCompanies.length,
companies: tagCompanies
});
}
}
allTags.sort((a, b) => b.count - a.count);
return allTags;
};
================================================
FILE: src/_config/events/build-css.js
================================================
import fs from 'node:fs/promises';
import path from 'node:path';
import postcss from 'postcss';
import postcssImport from 'postcss-import';
import postcssImportExtGlob from 'postcss-import-ext-glob';
import tailwindcss from 'tailwindcss';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import fg from 'fast-glob';
const buildCss = async (inputPath, outputPaths) => {
const inputContent = await fs.readFile(inputPath, 'utf-8');
const result = await postcss([
postcssImportExtGlob,
postcssImport,
tailwindcss,
autoprefixer,
cssnano
]).process(inputContent, {from: inputPath});
for (const outputPath of outputPaths) {
await fs.mkdir(path.dirname(outputPath), {recursive: true});
await fs.writeFile(outputPath, result.css);
}
return result.css;
};
export const buildAllCss = async () => {
const tasks = [];
tasks.push(buildCss('src/assets/css/global/global.css', ['src/_includes/css/global.css']));
const localCssFiles = await fg(['src/assets/css/local/**/*.css']);
for (const inputPath of localCssFiles) {
const baseName = path.basename(inputPath);
tasks.push(buildCss(inputPath, [`src/_includes/css/${baseName}`]));
}
const componentCssFiles = await fg(['src/assets/css/components/**/*.css']);
for (const inputPath of componentCssFiles) {
const baseName = path.basename(inputPath);
tasks.push(buildCss(inputPath, [`dist/assets/css/components/${baseName}`]));
}
await Promise.all(tasks);
};
================================================
FILE: src/_config/events/build-js.js
================================================
import fs from 'node:fs/promises';
import path from 'node:path';
import fg from 'fast-glob';
import esbuild from 'esbuild';
export const buildJs = async (inputPath, outputPath) => {
const result = await esbuild.build({
target: 'es2020',
entryPoints: [inputPath],
bundle: true,
minify: true,
write: false
});
const output = result.outputFiles[0].text;
await fs.mkdir(path.dirname(outputPath), {recursive: true});
await fs.writeFile(outputPath, output);
return output;
};
export const buildAllJs = async () => {
const tasks = [];
const inlineBundleFiles = await fg(['src/assets/scripts/bundle/**/*.js']);
for (const inputPath of inlineBundleFiles) {
const baseName = path.basename(inputPath);
const outputPath = `src/_includes/scripts/${baseName}`;
tasks.push(buildJs(inputPath, outputPath));
}
const componentFiles = await fg(['src/assets/scripts/components/**/*.js']);
for (const inputPath of componentFiles) {
const baseName = path.basename(inputPath);
const outputPath = `dist/assets/scripts/components/${baseName}`;
tasks.push(buildJs(inputPath, outputPath));
}
await Promise.all(tasks);
};
================================================
FILE: src/_config/events/svg-to-jpeg.js
================================================
import {promises as fsPromises, existsSync} from 'node:fs';
import path from 'node:path';
import Image from '@11ty/eleventy-img';
const ogImagesDir = './src/assets/og-images';
export const svgToJpeg = async () => {
const socialPreviewImagesDir = 'dist/assets/og-images/';
if (!existsSync(socialPreviewImagesDir)) {
console.log('⚠ No OG images dir found');
return;
}
const files = await fsPromises.readdir(socialPreviewImagesDir);
if (files.length > 0) {
files.forEach(async function (filename) {
const outputFilename = filename.substring(0, filename.length - 4);
if (filename.endsWith('.svg') & !existsSync(path.join(ogImagesDir, outputFilename))) {
const imageUrl = socialPreviewImagesDir + filename;
await Image(imageUrl, {
formats: ['jpeg'],
outputDir: ogImagesDir,
filenameFormat: function (id, src, width, format, options) {
return `${outputFilename}.${format}`;
}
});
}
});
} else {
console.log('⚠ No images found on OG images dir');
}
};
================================================
FILE: src/_config/events.js
================================================
import {svgToJpeg} from './events/svg-to-jpeg.js';
import {buildAllCss} from './events/build-css.js';
import {buildAllJs} from './events/build-js.js';
export default {
svgToJpeg,
buildAllCss,
buildAllJs
};
================================================
FILE: src/_config/filters/dates.js
================================================
import dayjs from 'dayjs';
/** Converts the given date string to ISO8610 format. */
export const toISOString = dateString => dayjs(dateString).toISOString();
/** Formats a date using dayjs's conventions: https://day.js.org/docs/en/display/format */
export const formatDate = (date, format) => dayjs(date).format(format);
================================================
FILE: src/_config/filters/fileExists.js
================================================
import fs from 'fs';
import path from 'path';
export const fileExists = (filePath) => {
try {
return fs.existsSync(path.resolve(process.cwd(), filePath));
} catch (e) {
return false;
}
};
================================================
FILE: src/_config/filters/markdown-format.js
================================================
// by Chris Burnell: https://chrisburnell.com/article/some-eleventy-filters/#markdown-format
import markdownParser from 'markdown-it';
const markdown = markdownParser();
export const markdownFormat = string => {
return markdown.render(string);
};
================================================
FILE: src/_config/filters/slugify.js
================================================
import slugify from 'slugify';
/** Converts string to a slug form. */
export const slugifyString = str => {
return slugify(str, {
replacement: '-',
remove: /[#,&,+()$~%.'":*¿?¡!<>{}]/g,
lower: true
});
};
================================================
FILE: src/_config/filters/sort-alphabetic.js
================================================
export const sortAlphabetically = array => {
return array.sort((a, b) => {
if (a.data.title < b.data.title) return -1;
if (a.data.title > b.data.title) return 1;
return 0;
});
};
================================================
FILE: src/_config/filters/sort-random.js
================================================
export const shuffleArray = array => {
const shuffled = [...array];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
};
================================================
FILE: src/_config/filters/split.js
================================================
/**
* Split a string by a delimiter, filtering out empty strings
* @param {string} str - String to split
* @param {string} delimiter - Delimiter to split by
* @returns {string[]} Array of non-empty parts
*/
export function split(str, delimiter = '/') {
if (!str) return [];
return str.split(delimiter).filter(part => part.length > 0);
}
================================================
FILE: src/_config/filters/splitlines.js
================================================
export const splitlines = (input, maxCharLength) => {
const parts = input.split(' ');
const lines = parts.reduce(function (acc, cur) {
if (!acc.length) {
return [cur];
}
let lastOne = acc[acc.length - 1];
if (lastOne.length + cur.length >= maxCharLength) {
return [...acc, cur];
}
acc[acc.length - 1] = lastOne + ' ' + cur;
return acc;
}, []);
return lines;
};
================================================
FILE: src/_config/filters/striptags.js
================================================
// Using single-character replacement as recommended by CodeQL to avoid
// incomplete multi-character sanitization vulnerabilities
export const striptags = string => {
if (!string) return '';
return String(string).replace(/<|>/g, '');
};
================================================
FILE: src/_config/filters.js
================================================
import {toISOString, formatDate} from './filters/dates.js';
import {markdownFormat} from './filters/markdown-format.js';
import {sortAlphabetically} from './filters/sort-alphabetic.js';
import {splitlines} from './filters/splitlines.js';
import {striptags} from './filters/striptags.js';
import {slugifyString} from './filters/slugify.js';
import {fileExists} from './filters/fileExists.js';
import {split} from './filters/split.js';
export default {
toISOString,
formatDate,
markdownFormat,
splitlines,
striptags,
sortAlphabetically,
fileExists,
slugifyString,
split
};
================================================
FILE: src/_config/plugins/drafts.js
================================================
export const drafts = eleventyConfig => {
eleventyConfig.addGlobalData('eleventyComputed.permalink', function () {
return data => {
// Always skip during non-watch/serve builds
if (data.draft && !process.env.BUILD_DRAFTS) {
return false; // Ensure templates that use this handle it correctly
}
return data.permalink;
};
});
// When `eleventyExcludeFromCollections` is true, the file is not included in any collections
eleventyConfig.addGlobalData('eleventyComputed.eleventyExcludeFromCollections', function () {
return data => {
// Always exclude from non-watch/serve builds
if (data.draft && !process.env.BUILD_DRAFTS) {
return true;
}
return data.eleventyExcludeFromCollections ?? false;
};
});
eleventyConfig.on('eleventy.before', ({runMode}) => {
// Set the environment variable
if (runMode === 'serve' || runMode === 'watch') {
process.env.BUILD_DRAFTS = true;
}
});
};
================================================
FILE: src/_config/plugins/html-config.js
================================================
import htmlmin from 'html-minifier-terser';
const isProduction = process.env.ELEVENTY_ENV === 'production';
export const htmlConfig = eleventyConfig => {
eleventyConfig.addTransform('html-minify', (content, path) => {
if (path && path.endsWith('.html') && isProduction) {
return htmlmin.minify(content, {
collapseBooleanAttributes: true,
collapseWhitespace: true,
decodeEntities: true,
includeAutoGeneratedTags: false,
removeComments: true
});
}
return content;
});
};
================================================
FILE: src/_config/plugins/markdown.js
================================================
import markdownIt from 'markdown-it';
import markdownItAttrs from 'markdown-it-attrs';
import markdownItPrism from 'markdown-it-prism';
import markdownItAnchor from 'markdown-it-anchor';
import markdownItClass from '@toycode/markdown-it-class';
import markdownItLinkAttributes from 'markdown-it-link-attributes';
import {full as markdownItEmoji} from 'markdown-it-emoji';
import markdownItFootnote from 'markdown-it-footnote';
import markdownitMark from 'markdown-it-mark';
import markdownitAbbr from 'markdown-it-abbr';
import {slugifyString} from '../filters/slugify.js';
export const markdownLib = markdownIt({
html: true,
breaks: true,
linkify: true,
typographer: true
})
.disable('code')
.use(markdownItAttrs)
.use(markdownItPrism, {
defaultLanguage: 'plaintext'
})
.use(markdownItAnchor, {
slugify: slugifyString,
tabIndex: false,
permalink: markdownItAnchor.permalink.headerLink({
class: 'heading-anchor'
})
})
.use(markdownItClass, {})
.use(markdownItLinkAttributes, [
{
// match external links
matcher(href) {
return href.match(/^https?:\/\//);
},
attrs: {
rel: 'noopener'
}
}
])
.use(markdownItEmoji)
.use(markdownItFootnote)
.use(markdownitMark)
.use(markdownitAbbr)
.use(md => {
md.renderer.rules.image = (tokens, idx) => {
const token = tokens[idx];
const src = token.attrGet('src');
const alt = token.content || '';
const caption = token.attrGet('title');
// Collect attributes
const attributes = token.attrs || [];
const hasEleventyWidths = attributes.some(([key]) => key === 'eleventy:widths');
if (!hasEleventyWidths) {
attributes.push(['eleventy:widths', '650,960,1400']);
}
const attributesString = attributes.map(([key, value]) => `${key}="${value}"`).join(' ');
const imgTag = `<img src="${src}" alt="${alt}" ${attributesString}>`;
return caption ? `<figure>${imgTag}<figcaption>${caption}</figcaption></figure>` : imgTag;
};
});
================================================
FILE: src/_config/plugins.js
================================================
// Eleventy
import {EleventyRenderPlugin} from '@11ty/eleventy';
import rss from '@11ty/eleventy-plugin-rss';
import syntaxHighlight from '@11ty/eleventy-plugin-syntaxhighlight';
import webc from '@11ty/eleventy-plugin-webc';
import {eleventyImageTransformPlugin} from '@11ty/eleventy-img';
// custom
import {markdownLib} from './plugins/markdown.js';
import {drafts} from './plugins/drafts.js';
// Custom transforms
import {htmlConfig} from './plugins/html-config.js';
export default {
EleventyRenderPlugin,
rss,
syntaxHighlight,
webc,
eleventyImageTransformPlugin,
markdownLib,
drafts,
htmlConfig
};
================================================
FILE: src/_config/setup/create-colors.js
================================================
import fs from 'node:fs';
import Color from 'colorjs.io';
const colorsBase = JSON.parse(fs.readFileSync('./src/_data/designTokens/colorsBase.json', 'utf-8'));
const generatePalette = (baseColorHex, steps) => {
const baseColor = new Color(baseColorHex).to('oklch');
return steps.map(step => {
const color = new Color('oklch', [step.lightness, baseColor.c * step.chromaFactor, baseColor.h]).to(
'srgb'
);
const [r, g, b] = color.coords.map(value => Math.round(Math.min(Math.max(value * 255, 0), 255)));
const hexValue = `#${r.toString(16).padStart(2, '0')}${g
.toString(16)
.padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
return {
name: `${step.label}`,
value: hexValue
};
});
};
const vibrantSteps = [
{label: '100', lightness: 0.96, chromaFactor: 0.19},
{label: '200', lightness: 0.94, chromaFactor: 0.45},
{label: '300', lightness: 0.86, chromaFactor: 0.78},
{label: '400', lightness: 0.75, chromaFactor: 0.9},
{label: '500', lightness: 0.62, chromaFactor: 1},
{label: '600', lightness: 0.5, chromaFactor: 1},
{label: '700', lightness: 0.42, chromaFactor: 1},
{label: '800', lightness: 0.36, chromaFactor: 0.85},
{label: '900', lightness: 0.2, chromaFactor: 0.55}
];
const neutralSteps = [
{label: '100', lightness: 0.98, chromaFactor: 0.12},
{label: '200', lightness: 0.92, chromaFactor: 0.14},
{label: '300', lightness: 0.75, chromaFactor: 0.14},
{label: '400', lightness: 0.6, chromaFactor: 0.25},
{label: '500', lightness: 0.5, chromaFactor: 0.3},
{label: '600', lightness: 0.4, chromaFactor: 0.35},
{label: '700', lightness: 0.35, chromaFactor: 0.3},
{label: '800', lightness: 0.3, chromaFactor: 0.27},
{label: '900', lightness: 0.2, chromaFactor: 0.25}
];
const colorTokens = {
title: colorsBase.title,
description: colorsBase.description,
items: []
};
colorsBase.shades_neutral.forEach(color => {
const palette = generatePalette(color.value, neutralSteps);
palette.forEach(shade => {
colorTokens.items.push({
name: `${color.name} ${shade.name}`,
value: shade.value
});
});
});
colorsBase.shades_vibrant.forEach(color => {
const palette = generatePalette(color.value, vibrantSteps);
palette.forEach(shade => {
colorTokens.items.push({
name: `${color.name} ${shade.name}`,
value: shade.value
});
});
});
colorsBase.light_dark.forEach(color => {
colorTokens.items.push({
name: color.name,
value: color.value
});
const lightDark = new Color(color.value).to('oklch');
const subduedColor = new Color('oklch', [
lightDark.l,
lightDark.c * 0.8, // reduce chroma by 20%
lightDark.h
]).to('srgb');
const [r, g, b] = subduedColor.coords.map(value => Math.round(Math.min(Math.max(value * 255, 0), 255)));
const subduedHex = `#${r.toString(16).padStart(2, '0')}${g
.toString(16)
.padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
colorTokens.items.push({
name: `${color.name} Subdued`,
value: subduedHex
});
});
colorsBase.standalone.forEach(color => {
colorTokens.items.push({
name: color.name,
value: color.value
});
});
fs.writeFileSync('./src/_data/designTokens/colors.json', JSON.stringify(colorTokens, null, 2));
================================================
FILE: src/_config/setup/generate-favicons.js
================================================
import fs from 'node:fs';
import sharp from 'sharp';
import {sharpsToIco} from 'sharp-ico';
import {pathToSvgLogo} from '../../_data/meta.js';
async function createFavicons() {
const outputDir = 'src/assets/images/favicon';
fs.mkdirSync(outputDir, {recursive: true});
// Get the SVG logo
const svgBuffer = fs.readFileSync(pathToSvgLogo);
// SVG icon
fs.writeFileSync(`${outputDir}/favicon.svg`, svgBuffer);
// PNG icons
await sharp(svgBuffer).resize(192, 192).toFile(`${outputDir}/icon-192x192.png`);
await sharp(svgBuffer).resize(512, 512).toFile(`${outputDir}/icon-512x512.png`);
await sharp(svgBuffer).resize(180, 180).toFile(`${outputDir}/apple-touch-icon.png`);
// maskable icon
await sharp(svgBuffer)
.resize(512, 512)
.extend({
top: 50,
bottom: 50,
left: 50,
right: 50,
background: {r: 0, g: 0, b: 0, alpha: 0} // Transparent padding
})
.toFile(`${outputDir}/maskable-icon.png`);
// ICO icon
const iconSharp = sharp(svgBuffer);
await sharpsToIco([iconSharp], `${outputDir}/favicon.ico`, {sizes: [32]});
console.log('All favicons generated.');
}
createFavicons();
================================================
FILE: src/_config/shortcodes/image.js
================================================
import Image from '@11ty/eleventy-img';
import path from 'node:path';
import fs from 'fs';
const stringifyAttributes = attributeMap => {
return Object.entries(attributeMap)
.map(([attribute, value]) => {
if (typeof value === 'undefined') return '';
return `${attribute}="${value}"`;
})
.join(' ');
};
export const imageShortcode = async (
src,
alt = '',
caption = '',
loading = 'lazy',
containerClass,
imageClass,
widths = [650, 960, 1400],
sizes = 'auto',
formats = ['avif', 'webp', 'jpeg']
) => {
// Prepend "./src" if not present
if (!src.startsWith('./src')) {
src = `./src${src}`;
}
// Check if file exists
if (!fs.existsSync(src)) {
console.warn(`Image not found: ${src}`);
return '';
}
const metadata = await Image(src, {
widths: [...widths],
formats: [...formats],
urlPath: '/assets/images/',
outputDir: './dist/assets/images/',
filenameFormat: (id, src, width, format, options) => {
const extension = path.extname(src);
const name = path.basename(src, extension);
return `${name}-${width}w.${format}`;
}
});
const lowsrc = metadata.jpeg[metadata.jpeg.length - 1];
const imageSources = Object.values(metadata)
.map(imageFormat => {
return ` <source type="${imageFormat[0].sourceType}" srcset="${imageFormat
.map(entry => entry.srcset)
.join(', ')}" sizes="${sizes}">`;
})
.join('\n');
const imageAttributes = stringifyAttributes({
'src': lowsrc.url,
'width': lowsrc.width,
'height': lowsrc.height,
alt,
loading,
'decoding': loading === 'eager' ? 'sync' : 'async',
...(imageClass && {class: imageClass}),
'eleventy:ignore': ''
});
const pictureElement = `<picture> ${imageSources}<img ${imageAttributes}></picture>`;
return caption
? `<figure slot="image"${containerClass ? ` class="${containerClass}"` : ''}>${pictureElement}<figcaption>${caption}</figcaption></figure>`
: `<picture slot="image"${containerClass ? ` class="${containerClass}"` : ''}>${imageSources}<img ${imageAttributes}></picture>`;
};
================================================
FILE: src/_config/shortcodes/svg.js
================================================
/**
* Generates an optimized SVG shortcode with optional attributes.
*
* @param {string} svgName - The name of the SVG file (without the .svg extension).
* @param {string} [ariaName=''] - The ARIA label for the SVG.
* @param {string} [className=''] - The CSS class name for the SVG.
* @param {string} [styleName=''] - The inline style for the SVG.
* @returns {Promise<string>} The optimized SVG shortcode.
*/
import {optimize} from 'svgo';
import {readFileSync} from 'node:fs';
export const svgShortcode = async (svgName, ariaName = '', className = '', styleName = '') => {
const svgData = readFileSync(`./src/assets/svg/${svgName}.svg`, 'utf8');
const {data} = await optimize(svgData);
return data.replace(
/<svg(.*?)>/,
`<svg$1 ${ariaName ? `aria-label="${ariaName}"` : 'aria-hidden="true"'} ${className ? `class="${className}"` : ''} ${styleName ? `style="${styleName}"` : ''} >`
);
};
================================================
FILE: src/_config/shortcodes.js
================================================
import {imageShortcode} from './shortcodes/image.js';
import {svgShortcode} from './shortcodes/svg.js';
export default {imageShortcode, svgShortcode};
================================================
FILE: src/_config/utils/clamp-generator.js
================================================
/**
* Credits:
* - © Andy Bell - https://buildexcellentwebsit.es/
*/
/**
* Takes an array of tokens and sends back and array of name
* and clamp pairs for CSS fluid values.
*
* @param {array} tokens array of {name: string, min: number, max: number}
* @returns {array} {name: string, value: string}
*/
import viewports from '../../_data/designTokens/viewports.json';
export const clampGenerator = tokens => {
const rootSize = 16;
return tokens.map(({name, min, max}) => {
if (min === max) {
return {name, value:`${min / rootSize}rem`};
}
// Convert the min and max sizes to rems
const minSize = min / rootSize;
const maxSize = max / rootSize;
// Convert the pixel viewport sizes into rems
const minViewport = viewports.min / rootSize;
const maxViewport = viewports.max / rootSize;
// Slope and intersection allow us to have a fluid value but also keep that sensible
const slope = (maxSize - minSize) / (maxViewport - minViewport);
const intersection = -1 * minViewport * slope + minSize;
return {
name,
value: `clamp(${minSize}rem, ${intersection.toFixed(2)}rem + ${(slope * 100).toFixed(
2
)}vw, ${maxSize}rem)`
};
});
};
================================================
FILE: src/_config/utils/tokens-to-tailwind.js
================================================
/**
* Credits:
* - © Andy Bell - https://buildexcellentwebsit.es/
*/
/**
* Converts human readable tokens into tailwind config friendly ones
*
* @param {array} tokens {name: string, value: any}
* @return {object} {key, value}
*/
import slugify from 'slugify';
export const tokensToTailwind = tokens => {
const nameSlug = text => slugify(text, {lower: true});
let response = {};
tokens.forEach(({name, value}) => {
response[nameSlug(name)] = value;
});
return response;
};
================================================
FILE: src/_data/changelog.json
================================================
[
{
"version": "4.4.1",
"date": "2026-02-22",
"summary": "Date field documentation",
"changes": [
{
"type": "changed",
"description": "Updated CLAUDE.md, contributing guide, PR template, and validation workflow to document addedAt/updatedAt fields"
}
]
},
{
"version": "4.4.0",
"date": "2026-02-22",
"summary": "Accurate company dates from git history",
"changes": [
{
"type": "changed",
"description": "Backfilled accurate addedAt/updatedAt dates for all 850+ company profiles from a decade of git history"
},
{
"type": "fixed",
"description": "Build time reduced from ~29s to ~18s by storing dates in frontmatter instead of computing from git on every build"
}
]
},
{
"version": "4.3.2",
"date": "2026-02-22",
"summary": "Fairer homepage rotation",
"changes": [
{
"type": "fixed",
"description": "Replaced biased shuffle with Fisher-Yates algorithm for featured and recently added companies"
}
]
},
{
"version": "4.3.1",
"date": "2026-02-22",
"summary": "Easter egg removal",
"changes": [
{
"type": "removed",
"description": "Removed easter egg confetti component"
}
]
},
{
"version": "4.3.0",
"date": "2026-02-22",
"summary": "Memory leak fix",
"changes": [
{
"type": "fixed",
"description": "Fixed event listener memory leak in masonry grid web component"
}
]
},
{
"version": "4.2.0",
"date": "2026-02-15",
"summary": "Blog post and discoverability improvements",
"changes": [
{
"type": "added",
"description": "Blog post: Keeping Things Tidy"
},
{
"type": "changed",
"description": "Improved site discoverability over GitHub repo"
},
{
"type": "changed",
"description": "Skip PR validation for repo owner, members, and bots"
}
]
},
{
"version": "4.1.2",
"date": "2026-02-06",
"summary": "PR validation and housekeeping",
"changes": [
{
"type": "added",
"description": "Automated PR validation for company profile contributions"
},
{
"type": "removed",
"description": "Removed defunct companies for various reasons (closed, acquired, no longer relevant, etc.)"
},
{
"type": "fixed",
"description": "Fixed broken URLs across several company profiles"
}
]
},
{
"version": "4.1.1",
"date": "2026-01-18",
"summary": "Search, dates, and social sharing improvements",
"changes": [
{
"type": "added",
"description": "Pagefind-powered site search with nav dropdown and dedicated search page"
},
{
"type": "added",
"description": "Git-based dates for company profiles (added at / last updated)"
},
{
"type": "added",
"description": "Open Graph images for social sharing"
},
{
"type": "changed",
"description": "Updated company profile frontmatter and documentation"
}
]
},
{
"version": "4.1.0",
"date": "2026-01-17",
"summary": "New companies, SEO improvements, and security hardening",
"changes": [
{
"type": "added",
"description": "Company: MaintainNow"
},
{
"type": "added",
"description": "Company: Swif.ai"
},
{
"type": "added",
"description": "Company: Verve Systems"
},
{
"type": "added",
"description": "Blog post: SEO Improvements"
},
{
"type": "changed",
"description": "SEO improvements and bulk company URL fixes across all profiles"
},
{
"type": "fixed",
"description": "Security hardening (CodeQL sanitisation, template escaping)"
},
{
"type": "removed",
"description": "Cleaned up legacy files and GitHub Actions workflows"
}
]
},
{
"version": "4.0.0",
"date": "2026-01-13",
"summary": "Complete site redesign on Eleventy v3",
"changes": [
{
"type": "added",
"description": "Full site redesign with company cards, browse/filter pages, and curated homepage"
},
{
"type": "added",
"description": "Structured company profiles with region, remote policy, size, and technology tags"
},
{
"type": "added",
"description": "Blog post: The Big Redesign"
},
{
"type": "changed",
"description": "Migrated from static README-based list to Eleventy v3 static site"
},
{
"type": "added",
"description": "Legacy URL redirects and 404 tracking with Fathom Analytics"
}
]
}
]
================================================
FILE: src/_data/companyHelpers.js
================================================
/**
* Helper functions and constants for company data.
* Label maps are imported from labels.js (the single source of truth).
*/
import labels from './labels.js';
const l = labels();
// Re-export label maps for JS consumers (collections.js, companyTags.js, etc.)
export const regionLabels = l.region;
export const remotePolicyLabels = l.remotePolicy;
export const companySizeLabels = l.companySize;
export const techLabels = l.tech;
export function getRegionLabel(region) {
return regionLabels[region] || region || 'Other';
}
export function getRemotePolicyLabel(policy) {
return remotePolicyLabels[policy] || policy || 'Unknown';
}
export function getCompanySizeLabel(size) {
return companySizeLabels[size] || size || 'Unknown';
}
export function getTechLabel(tech) {
return techLabels[tech] || tech;
}
/**
* Featured companies list - manually curated high-quality examples
*/
export const featuredCompanySlugs = [
'github',
'gitlab',
'automattic',
'zapier',
'buffer',
'doist',
'elastic',
'hashicorp',
'stripe',
'shopify',
'netlify',
'vercel'
];
================================================
FILE: src/_data/designTokens/borderRadius.json
================================================
{
"title": "Border Radius",
"description": "",
"meta": {},
"items": [
{
"name": "small",
"value": "0.1875rem"
},
{
"name": "Medium",
"value": "0.3rem"
}
]
}
================================================
FILE: src/_data/designTokens/colors.json
================================================
{
"title": "Colors",
"description": "Hex color codes that can be shared, cross-platform. They can be converted at point of usage, such as HSL for web or CMYK for print. neutral and vibrant colors are converted to color palettes, fixed colors are kept as they are",
"items": [
{
"name": "Gray 100",
"value": "#f6f9fc"
},
{
"name": "Gray 200",
"value": "#e2e5e8"
},
{
"name": "Gray 300",
"value": "#acaeb2"
},
{
"name": "Gray 400",
"value": "#7c8086"
},
{
"name": "Gray 500",
"value": "#5f646a"
},
{
"name": "Gray 600",
"value": "#434850"
},
{
"name": "Gray 700",
"value": "#373b41"
},
{
"name": "Gray 800",
"value": "#2a2e33"
},
{
"name": "Gray 900",
"value": "#13161b"
},
{
"name": "Indigo",
"value": "#6366F1"
},
{
"name": "Indigo Subdued",
"value": "#666eda"
},
{
"name": "Teal",
"value": "#14B8A6"
},
{
"name": "Teal Subdued",
"value": "#4cb4a5"
},
{
"name": "Cyan",
"value": "#22D3EE"
},
{
"name": "Cyan Subdued",
"value": "#5ccfe4"
},
{
"name": "Periwinkle",
"value": "#818CF8"
},
{
"name": "Periwinkle Subdued",
"value": "#8590e5"
},
{
"name": "Dark",
"value": "#090E1A"
},
{
"name": "Light",
"value": "#F5F7FF"
}
]
}
================================================
FILE: src/_data/designTokens/colorsBase.json
================================================
{
"title": "Colors",
"description": "Hex color codes that can be shared, cross-platform. They can be converted at point of usage, such as HSL for web or CMYK for print. neutral and vibrant colors are converted to color palettes, fixed colors are kept as they are",
"shades_neutral": [
{
"name": "Gray",
"value": "#64748B"
}
],
"shades_vibrant": [],
"light_dark": [
{
"name": "Indigo",
"value": "#6366F1"
},
{
"name": "Teal",
"value": "#14B8A6"
},
{
"name": "Cyan",
"value": "#22D3EE"
},
{
"name": "Periwinkle",
"value": "#818CF8"
}
],
"standalone": [
{
"name": "Dark",
"value": "#090E1A"
},
{
"name": "Light",
"value": "#F5F7FF"
}
]
}
================================================
FILE: src/_data/designTokens/fonts.json
================================================
{
"title": "Fonts",
"description": "Each array of fonts creates a priority-based order. The first font in the array should be the ideal font, followed by sensible, web-safe fallbacks",
"items": [
{
"name": "Display",
"description": "Display font stack for headings and large text. Redhat Display is made for headlines and big statements, are low contrast and spaced tightly, with a large x-height and open counters.",
"value": ["Redhat", "Segoe UI", "Roboto", "Helvetica Neue", "Arial", "sans-serif"]
},
{
"name": "Base",
"description": "Base font stack for body text. Atkinson Hyperlegible, named after the founder of the Braille Institute, has been developed specifically to increase legibility for readers with low vision, and to improve comprehension.",
"value": ["Atkinson Hyperlegible", "system-ui", "sans-serif"]
},
{
"name": "Mono",
"description": "Monospace font for code and preformatted text.",
"value": [
"ui-monospace",
"Cascadia Code",
"Source Code Pro",
"Menlo",
"Consolas",
"DejaVu Sans Mono",
"monospace"
]
}
]
}
================================================
FILE: src/_data/designTokens/spacing.json
================================================
{
"title": "Spacing",
"description": "Consistent spacing sizes, based on a ratio, with min and max sizes. This allows you to set spacing based on the context size. For example, min for mobile and max for desktop browsers.",
"meta": {
"scaleGenerator": "https://utopia.fyi/space/calculator?c=320,19,1.2,1350,28,1.25,6,2,&s=0.75%7C0.5%7C0.25,2%7C3%7C5%7C8%7C13,s-l&g=s,l,xl,12",
"note": "shifing the scale: XS is equal to 3XS"
},
"items": [
{
"name": "3XS",
"min": 2,
"max": 3
},
{
"name": "2XS",
"min": 3,
"max": 5
},
{
"name": "XS",
"min": 5,
"max": 7
},
{
"name": "S",
"min": 10,
"max": 14
},
{
"name": "M",
"min": 14,
"max": 21
},
{
"name": "L",
"min": 19,
"max": 28
},
{
"name": "XL",
"min": 38,
"max": 56
},
{
"name": "2XL",
"min": 57,
"max": 84
},
{
"name": "3XL",
"min": 95,
"max": 140
},
{
"name": "XS - S",
"min": 5,
"max": 14
},
{
"name": "S - M",
"min": 10,
"max": 21
},
{
"name": "M - L",
"min": 14,
"max": 31
},
{
"name": "L - XL",
"min": 19,
"max": 56
},
{
"name": "L - 2xl",
"min": 38,
"max": 84
},
{
"name": "XL - 2XL",
"min": 57,
"max": 140
},
{
"name": "2XL - 3xl",
"min": 95,
"max": 224
}
]
}
================================================
FILE: src/_data/designTokens/textLeading.json
================================================
{
"title": "Leading",
"description": "Ratio-based leading/line-height values",
"items": [
{
"name": "Flat",
"value": 1
},
{
"name": "Fine",
"value": 1.2
},
{
"name": "Standard",
"value": 1.4
}
]
}
================================================
FILE: src/_data/designTokens/textSizes.json
================================================
{
"title": "Text Sizes",
"description": "A minimum and maximum text size size allows you to pick the right size from a ratio, depending on the context size. The min and max sizes are in pixels and should be converted to appropiate sizes, per context",
"meta": {
"scaleGenerator": "https://utopia.fyi/type/calculator?c=320,19,1.2,1350,28,1.25,6,2,&s=0.75|0.5|0.25,2|3|5|8|13,s-l&g=s,l,xl,12"
},
"items": [
{
"name": "Step min 2",
"min": 13,
"max": 16
},
{
"name": "Step min 1",
"min": 16,
"max": 22
},
{
"name": "Step 0",
"min": 19,
"max": 28
},
{
"name": "Step 1",
"min": 23,
"max": 35
},
{
"name": "Step 2",
"min": 27,
"max": 44
},
{
"name": "Step 3",
"min": 33,
"max": 55
},
{
"name": "Step 4",
"min": 40,
"max": 68
},
{
"name": "Step 5",
"min": 47,
"max": 86
},
{
"name": "Step 6",
"min": 56,
"max": 107
}
]
}
================================================
FILE: src/_data/designTokens/textWeights.json
================================================
{
"title": "Text Weights",
"description": "Helper classes and custom properties for common font weights",
"meta": {},
"items": [
{
"name": "Regular",
"value": 400
},
{
"name": "Bold",
"value": 700
},
{
"name": "Extra Bold",
"value": 900
}
]
}
================================================
FILE: src/_data/designTokens/viewports.json
================================================
{
"title": "Viewports",
"description": "The min and maximum viewports used to generate fluid type and space scales.",
"min": 320,
"sm": 640,
"navigation": 662,
"md": 1000,
"max": 1360
}
================================================
FILE: src/_data/github.js
================================================
import EleventyFetch from '@11ty/eleventy-fetch';
export default async function () {
let url = 'https://api.github.com/repos/remoteintech/remote-jobs';
// returning promise
let data = await EleventyFetch(url, {
duration: '1d',
type: 'json'
});
return data;
}
================================================
FILE: src/_data/helpers.js
================================================
/**
* Returns back some attributes based on whether the
* link is active or a parent of an active item.
*
* @param {String} itemUrl - The link in question.
* @param {String} pageUrl - The page context.
* @returns {String} - The attributes or empty.
*/
export function getLinkActiveState(itemUrl, pageUrl) {
let response = '';
// Ensure pageUrl is a string before proceeding
if (typeof pageUrl === 'string') {
if (itemUrl === pageUrl) {
response = ' aria-current="page"';
}
if (itemUrl.length > 1 && pageUrl.startsWith(itemUrl.replace('/page-0/', ''))) {
response += ' aria-current="page" data-state="active"';
}
}
return response;
}
/**
* Take an array of keys and return back items that match.
* Note: items in the collection must have a key attribute in
* Front Matter.
*
* @param {Array} collection - 11ty collection.
* @param {Array} keys - Collection of keys.
* @returns {Array} - Result collection or empty.
*/
export function filterCollectionByKeys(collection, keys) {
return collection.filter(x => keys.includes(x.data.key));
}
/**
* Generates a random UUID (Universally Unique Identifier).
*
* @returns {string} A random UUID.
*/
export function random() {
const segment = () => {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return `${segment()}-${segment()}-${segment()}`;
}
================================================
FILE: src/_data/labels.js
================================================
/**
* Centralised label definitions for regions, remote policies, technologies, and company sizes.
* This is the single source of truth — used by both JS (via import from companyHelpers.js)
* and Nunjucks templates (via the global data cascade as `labels.*`).
*/
export default function () {
return {
region: {
'worldwide': 'Worldwide',
'americas': 'Americas',
'europe': 'Europe',
'americas-europe': 'Americas & Europe',
'asia-pacific': 'Asia Pacific',
'other': 'Other'
},
remotePolicy: {
'fully-remote': 'Fully Remote',
'remote-first': 'Remote First',
'hybrid': 'Hybrid',
'remote-friendly': 'Remote Friendly'
},
companySize: {
'tiny': '1-10 employees',
'small': '11-50 employees',
'medium': '51-200 employees',
'large': '201-1000 employees',
'enterprise': '1000+ employees'
},
tech: {
'javascript': 'JavaScript',
'python': 'Python',
'ruby': 'Ruby',
'go': 'Go',
'java': 'Java',
'php': 'PHP',
'rust': 'Rust',
'dotnet': '.NET',
'elixir': 'Elixir',
'scala': 'Scala',
'cloud': 'Cloud',
'devops': 'DevOps',
'mobile': 'Mobile',
'data': 'Data',
'ml': 'ML/AI',
'sql': 'SQL',
'nosql': 'NoSQL',
'search': 'Search'
}
};
}
================================================
FILE: src/_data/meta.js
================================================
export const url = process.env.URL || 'http://localhost:8080';
// Extract domain from `url`
export const domain = new URL(url).hostname;
export const siteName = 'Remote In Tech';
export const siteDescription = 'A list of semi to fully remote-friendly companies in or around tech';
export const siteType = 'WebSite'; // schema
export const locale = 'en_EN';
export const lang = 'en';
export const skipContent = 'Skip to content';
export const author = {
name: 'Remote In Tech Community'
};
export const creator = {
name: 'Doug Aitken',
email: 'doug@dougaitken.co.uk',
website: 'https://dougaitken.link'
};
export const pathToSvgLogo = 'src/assets/svg/misc/logo.svg'; // used for favicon generation
export const themeColor = '#6366F1'; // used in manifest, for example primary color value
export const themeLight = '#F5F7FF'; // used for meta tag theme-color, if light colors are prefered. best use value set for light bg
export const themeDark = '#090E1A'; // used for meta tag theme-color, if dark colors are prefered. best use value set for dark bg
export const opengraph_default = '/assets/images/template/opengraph-default.jpg'; // fallback/default meta image
export const opengraph_default_alt =
"Remote In Tech - A list of semi to fully remote-friendly companies in or around tech"; // alt text for default meta image"
export const blog = {
// RSS feed
name: 'Remote In Tech',
description: 'A list of semi to fully remote-friendly companies in or around tech',
// feed links are looped over in the head. You may add more to the array.
feedLinks: [
{
title: 'Atom Feed',
url: '/feed.xml',
type: 'application/atom+xml'
},
{
title: 'JSON Feed',
url: '/feed.json',
type: 'application/json'
}
],
// Tags
tagSingle: 'Tag',
tagPlural: 'Tags',
tagMore: 'More tags:',
// pagination
paginationLabel: 'Blog',
paginationPage: 'Page',
paginationPrevious: 'Previous',
paginationNext: 'Next',
paginationNumbers: true
};
export const details = {
aria: 'section controls',
expand: 'expand all',
collapse: 'collapse all'
};
export const dialog = {
close: 'Close',
next: 'Next',
previous: 'Previous'
};
export const navigation = {
navLabel: 'Menu',
ariaTop: 'Main',
ariaBottom: 'Complementary',
ariaPlatforms: 'Platforms',
drawerNav: false,
subMenu: false
};
export const themeSwitch = {
title: 'Theme',
light: 'light',
dark: 'dark'
};
export const greenweb = {
// https://carbontxt.org/
disclosures: [
{
docType: 'sustainability-page',
url: `${url}/sustainability/`,
domain: domain
}
],
services: [{domain: 'netlify.com', serviceType: 'cdn'}]
};
export const viewRepo = {
// this is for the view/edit on github link. The value in the package.json will be pulled in.
allow: true,
infoText: 'View this page on GitHub'
};
export const analytics = {
// Fathom Analytics - privacy-focused analytics
// Only loads in production (ELEVENTY_ENV=production)
fathom: {
siteId: 'KVRCNWLT'
}
};
================================================
FILE: src/_data/navigation.js
================================================
export default {
top: [
{
text: 'Companies',
url: '/companies/'
},
{
text: 'Browse',
url: '/browse/'
},
{
text: 'About',
url: '/about/'
},
{
text: 'Blog',
url: '/blog/'
},
{
text: 'Contributing',
url: '/contributing/'
},
{
text: 'Contact',
url: '/contact/'
}
],
bottom: [
{
text: 'Privacy',
url: '/privacy/'
},
{
text: 'Changelog',
url: '/changelog/'
}
]
};
================================================
FILE: src/_data/personal.js
================================================
export const platforms = {
github: 'https://github.com/remoteintech/remote-jobs'
};
================================================
FILE: src/_includes/head/css-inline.njk
================================================
{%- if eleventy.env.runMode === "serve" %}
<!-- External CSS for local dev (refresh without page reload) -->
<link rel="stylesheet" href="{% getBundleFileUrl "css", "global" %}">
<link rel="stylesheet" href="{% getBundleFileUrl "css", "local" %}">
{%- else %}
<!-- Inlined CSS (fastest site performance in production) -->
<style>{% getBundle "css", "global" %}</style>
<style>{% getBundle "css", "local" %}</style>
{%- endif %}
{% css "global" %}{% include "css/global.css" %}{% endcss %}
================================================
FILE: src/_includes/head/js-defer.njk
================================================
<script src="{% getBundleFileUrl "js", "defer" %}" type="module"></script>
================================================
FILE: src/_includes/head/js-inline.njk
================================================
<script>{% getBundle "js", "inline" %}</script>
{%- js "inline" %} <!-- inlined for all pages -->
{% include "scripts/is-land.js" %}
{% include "scripts/theme-toggle.js" %}
{% endjs %}
================================================
FILE: src/_includes/head/meta-info.njk
================================================
{% set metaDescription %}
{%- if discover.description -%}
{{- discover.description -}}
{%- elif description -%}
{{- description -}}
{%- else -%}
{{- meta.siteDescription -}}
{%- endif -%}
{% endset %}
<meta name="description" content="{{ metaDescription }}" />
<meta name="theme-color" content="{{ meta.themeDark }}" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="{{ meta.themeLight }}" media="(prefers-color-scheme: light)" />
<!-- Search Engines -->
<meta name="robots" content="index,follow" />
<meta name="googlebot" content="index,follow" />
<!-- Made with Eleventy! -->
<meta name="generator" content="{{ eleventy.generator }}" />
<!-- Disable automatic detection and formatting of possible phone numbers -->
<meta name="format-detection" content="telephone=no" />
<!-- supports both dark and light color schemes, page author prefers light as default. -->
<meta name="color-scheme" content="light dark" />
<!-- activates page transitions -->
<meta name="view-transition" content="same-origin" />
<!-- Helps prevent duplicate content issues -->
<link rel="canonical" href="{{ meta.url }}{{ page.url }}" />
<!-- Links to information about the author(s) of the document -->
<link rel="author" href="humans.txt" />
{% if personal.platforms.mastodon %}
<!-- Mastodon verified site -->
<link rel="me" href="{{ personal.platforms.mastodon }}" />
{% endif %}
{% if meta.author.fediverse %}
<!-- fediverse creator tag -->
<meta name="fediverse:creator" content="{{ meta.author.fediverse }}" />
{% endif %}
<!-- Open Graph meta -->
<meta property="og:url" content="{{ meta.url }}{{ page.url }}" />
<meta property="og:type" content="website" />
<meta
property="og:title"
content="{%- if discover.title -%}
{{- discover.title -}}
{%- elif title -%}
{{- title -}}
{%- else -%}
{{- meta.siteName -}}
{%- endif -%}"
/>
<meta
property="og:image"
content="{%- if layout == 'post' -%}
{{- meta.url -}}/assets/og-images/{{ title | slugify }}-preview.jpeg
{%- else -%}
{{- meta.url -}}{{- meta.opengraph_default -}}
{%- endif -%}"
/>
<meta
property="og:image:alt"
content="{%- if layout == 'post' -%}
{{- title -}}
{%- else -%}
{{- meta.opengraph_default_alt -}}
{%- endif -%}"
/>
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:description" content="{{ metaDescription }}" />
<meta property="og:site_name" content="{{ meta.siteName }}" />
<meta property="og:locale" content="{{ meta.locale }}" />
<meta property="article:author" content="{{ meta.author.name }}" />
<!-- Twitter/X Card meta tags -->
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:title"
content="{%- if discover.title -%}
{{- discover.title -}}
{%- elif title -%}
{{- title -}}
{%- else -%}
{{- meta.siteName -}}
{%- endif -%}"
/>
<meta name="twitter:description" content="{{ metaDescription }}" />
<meta
name="twitter:image"
content="{%- if layout == 'post' -%}
{{- meta.url -}}/assets/og-images/{{ title | slugify }}-preview.jpeg
{%- else -%}
{{- meta.url -}}{{- meta.opengraph_default -}}
{%- endif -%}"
/>
<!-- RSS Autodiscovery. Loops over meta.blog.feedLinks -->
{% for feedLink in meta.blog.feedLinks %}
<link
rel="alternate"
type="{{ feedLink.type }}"
title="{{ feedLink.title }}: {{ meta.siteName }}"
href="{{ feedLink.url }}"
/>
{% endfor %}
<!-- Favicon: https://evilmartians.com/chronicles/how-to-favicon-in-2021-six-files-that-fit-most-needs -->
<link rel="icon" href="{{ '/favicon.ico' | url }}" sizes="any" />
<link rel="icon" href="{{ '/favicon.svg' | url }}" type="image/svg+xml" />
<link rel="apple-touch-icon" sizes="180x180" href="{{ '/apple-touch-icon.png' | url }}" />
<link rel="manifest" href="{{ '/site.webmanifest' | url }}" />
================================================
FILE: src/_includes/head/preloads.njk
================================================
<link
rel="preload"
href="/assets/fonts/redhat/red-hat-display-v7-latin-900.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link
rel="preload"
href="/assets/fonts/atkinson/atkinson-hyperlegible-regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<!-- preloads in page data -->
{%- if preloads -%}
<!-- prettier-ignore -->
<link rel="preload"
href="{{ preloads.href }}"
as="{{ preloads.as }}"
{% if preloads.type %}type={{ preloads.type }}{% endif %}{% if preloads.imagesrcset %}imagesrcset="{{ preloads.imagesrcset }}"{% endif %}
{% if preloads.crossorigin %}crossorigin{% endif %} />
{% endif %}
================================================
FILE: src/_includes/head/schema.njk
================================================
{% include "schemas/WebSite.njk" %}
{% include "schemas/BreadcrumbList.njk" %}
{% if schema %}
{%- include "schemas/" + schema + ".njk" -%}
{% endif %}
================================================
FILE: src/_includes/partials/card-tag.njk
================================================
{% set headingLevel = headingLevel | default("h2") %}
{% set definedDate = definedDate | default(item.date) %}
<custom-card clickable>
<{{ headingLevel }} slot="headline" class="text-step-2">
<a href="{{ item.url | url }}">{{ item.data.title }}</a>
</{{ headingLevel }}>
<span slot="date"> {% include "partials/date.njk" %} </span>
<p slot="content">{{ item.data.description }}</p>
</custom-card>
{% css "local" %}
{% include "css/custom-card.css" %}
{% endcss %}
================================================
FILE: src/_includes/partials/company-card.njk
================================================
{# Company Card Macro - Reusable card component for company listings #}
{% set regionLabels = {
'worldwide': 'Worldwide',
'americas': 'Americas',
'europe': 'Europe',
'americas-europe': 'Americas & Europe',
'asia-pacific': 'Asia Pacific',
'other': 'Other'
} %}
{% macro companyCard(company, showRegion = true, showWebsite = true) %}
<article class="company-card">
<h3 class="company-card__name">
<a href="{{ company.url }}">{{ company.data.title }}</a>
</h3>
{% if showRegion and company.data.region %}
<p class="company-card__region">{{ regionLabels[company.data.region] or company.data.region }}</p>
{% endif %}
{% set displayUrl = company.data.careers_url or company.data.website %}
{% if showWebsite and displayUrl %}
<p class="company-card__website">
<a href="{{ displayUrl }}" target="_blank" rel="noopener noreferrer" class="external-link" data-track-outbound="{{ company.data.title }}">
{{ displayUrl | replace("https://", "") | replace("http://", "") | truncate(35) }}
</a>
</p>
{% endif %}
</article>
{% endmacro %}
================================================
FILE: src/_includes/partials/date.njk
================================================
<time datetime="{{ definedDate | toIsoString }}"> {{ definedDate | formatDate('MMMM D, YYYY') }} </time>
================================================
FILE: src/_includes/partials/details.njk
================================================
<div class="details flow">
<div class="control | cluster" aria-label="{{ meta.details.aria }}">
<button id="expandAll" class="button" data-small-button>{{ meta.details.expand }}</button>
<button id="collapseAll" class="button" data-small-button>{{ meta.details.collapse }}</button>
</div>
{%- for item in itemList | alphabetic -%}
<details id="{{ item.data.title | slugify }}">
<summary>{{ item.data.title }}</summary>
{{- item.templateContent | safe -}}
</details>
{%- endfor -%}
</div>
{% css "local" %}
{% include "css/details.css" %}
{% endcss %}
{% js "defer" %}
{% include "scripts/details.js" %}
{% endjs %}
================================================
FILE: src/_includes/partials/edit-on.njk
================================================
{% if meta.viewRepo.allow %}
<hr />
<p class="text-step-min-1">
{{ meta[page.lang].blog.githubEdit }}
<a href="{{ pkg.repository.url | url | replace('.git', '/tree/main/') }}{{ page.inputPath }}"
>{{ meta.viewRepo.infoText }}</a
>.
</p>
{% endif %}
================================================
FILE: src/_includes/partials/footer.njk
================================================
<footer class="site-footer mt-l-xl p-s-m">
<div class="wrapper">
<div class="cluster gutter-xs">
<nav class="cluster text-step-0" aria-label="{{ meta.navigation.ariaBottom }}">
© {% year %}
<a href="/" {{ indicateActiveHome }}
><span class="font-display">{{ meta.siteName }}</span></a>
<a href="/changelog/" class="no-indicator"><small>v{{ pkg.version }}</small></a>
{% for item in navigation.bottom %}
<a
href="{{ item.url }}"
{{
helpers.getLinkActiveState(item.url,
page.url)
|
safe
}}
>{{ item.text }}</a
>
{% endfor %}
</nav>
<nav class="cluster" aria-label="{{ meta.navigation.ariaPlatforms }}">
<!-- gets the first item from the feed loop set in meta -->
<a href="{{ meta.blog.feedLinks[0].url }}" rel="alternate" type="{{ meta.blog.feedLinks[0].type }}">
<span class="visually-hidden">{{ meta.blog.feedLinks[0].title }}</span>
{% svg "misc/rss" %}
</a>
{% for key, value in personal.platforms %}
{% if value != "" %}
<!-- regular platforms -->
<a class="no-indicator" href="{{ value }}" rel="me">
<span class="visually-hidden">{{ key | capitalize }}</span>
{% svg 'platform/' + key %}
</a>
{% endif %}
{% endfor %}
</nav>
</div>
<aside class="creator | cluster gutter-2xs text-step-min-1 mt-xl">
Made with <span class="visually-hidden">love</span> <span>{% svg "misc/heart" %}</span> and
<a class="no-indicator" href="https://www.11ty.dev/">{% svg "misc/11ty", "Eleventy" %}</a> by
<a href="{{ meta.creator.website }}">{{ meta.creator.name }}</a>
</aside>
</div>
</footer>
================================================
FILE: src/_includes/partials/header.njk
================================================
<header class="wrapper">
<a href="#main" class="skip-link">{{ meta.skipContent }}</a>
<div class="repel ontop gutter-s">
<a href="/" class="logo | cluster" {{ indicateActiveHome }}>
{% svg "misc/logo" %}
<span class="font-display">{{ meta.siteName }}</span>
</a>
<div class="cluster gutter-s">
{% include "partials/main-nav.njk" %}
{% include "partials/nav-search.njk" %}
{% include 'partials/theme-switch.njk' %}
</div>
</div>
</header>
================================================
FILE: src/_includes/partials/main-nav.njk
================================================
<!-- toggle drawer and sub menu in _data/meta.js -->
{% set drawerNav = meta.navigation.drawerNav %}
{% set subMenu = meta.navigation.subMenu %}
<nav id="mainnav" class="mainnav" aria-label="{{ meta.navigation.ariaTop }}">
<ul class="cluster" role="list" no-flash>
{% for item in navigation.top %}
{% if item.submenu %}
<li class="relative">
<button
data-submenu-toggle
aria-expanded="false"
aria-labelledby="mainnav-{{ loop.index }}"
aria-controls="mainnav-{{ loop.index }}-sub"
>
{{ item.text }} {% svg "misc/chev-down" %}
</button>
<ul class="nav-sublist | cluster" id="mainnav-{{ loop.index }}-sub">
{% for subitem in item.submenu %}
<li>
<a
href="{{ subitem.url }}"
{{
helpers.getLinkActiveState(subitem.url, page.url)
| safe
}}
>{{ subitem.text }}</a
>
</li>
{% endfor %}
</ul>
</li>
{% else %}
<li>
<a
href="{{ item.url }}"
{{
helpers.getLinkActiveState(item.url,
page.url)
|
safe
}}
>{{ item.text }}</a
>
</li>
{% endif %}
{% endfor %}
</ul>
</nav>
{% if drawerNav %}
<!-- template element holding a button that needs to be injected when JavaScript is finally available. -->
<!-- based on solution by Manuel Matuzovic, https://web.dev/website-navigation/ and the Web Accessibility Cookbook -->
<!-- see also: https://kittygiraudel.com/2022/09/30/templating-in-html/ -->
<template id="burger-template">
<button data-drawer-toggle class="cluster" type="button" aria-expanded="false" aria-controls="mainnav">
<span>{{ meta.navigation.navLabel }}</span>
<svg
width="1em"
height="1em"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M5 12h14" />
<path d="M12 5v14" />
</svg>
</button>
</template>
{% css "local" %}
{% include "css/nav-main-drawer-cls.css" %}
{% endcss %}
{% js "defer" %}
{% include "scripts/nav-drawer.js" %}
{% endjs %}
{% endif %}
{% if subMenu %}
{% js "defer" %}
{% include "scripts/nav-sub.js" %}
{% endjs %}
{% endif %}
================================================
FILE: src/_includes/partials/nav-search.njk
================================================
<div class="nav-search" id="nav-search">
<button class="nav-search__trigger" aria-expanded="false" aria-controls="nav-search-dropdown" aria-label="Search companies">
{% svg "misc/search" %}
</button>
<div class="nav-search__dropdown" id="nav-search-dropdown" hidden>
<div id="nav-search-container"></div>
<a href="/search/" class="nav-search__view-all" id="nav-search-view-all" hidden>
View all results →
</a>
</div>
</div>
{%- css "global" -%}
.nav-search {
position: relative;
}
.nav-search__trigger {
display: inline-flex;
align-items: center;
padding: var(--space-xs);
color: var(--color-text);
background: transparent;
border: none;
border-radius: var(--border-radius-small);
cursor: pointer;
transition: background-color 0.2s ease;
}
.nav-search__trigger:hover {
background-color: var(--color-bg-accent);
}
.nav-search__trigger svg {
width: 1.2em;
height: 1.2em;
}
.nav-search__dropdown {
position: absolute;
top: calc(100% + var(--space-xs));
right: 0;
width: min(400px, 90vw);
background: var(--color-bg);
border: 1px solid var(--color-bg-accent-2);
border-radius: var(--border-radius-medium);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
padding: var(--space-s);
z-index: 100;
}
.nav-search__dropdown[hidden] {
display: none;
}
/* Pagefind UI overrides for nav search */
.nav-search__dropdown .pagefind-ui {
background-color: var(--color-bg) !important;
}
.nav-search__dropdown .pagefind-ui__search-input {
background-color: var(--color-bg) !important;
border-color: var(--color-bg-accent-2) !important;
color: var(--color-text) !important;
font-size: var(--size-step--1) !important;
}
.nav-search__dropdown .pagefind-ui__search-input::placeholder {
color: var(--color-text-accent) !important;
}
.nav-search__dropdown .pagefind-ui__drawer,
.nav-search__dropdown .pagefind-ui__results,
.nav-search__dropdown .pagefind-ui__results-area {
background-color: var(--color-bg) !important;
}
.nav-search__dropdown .pagefind-ui__result {
padding: var(--space-xs) !important;
background-color: var(--color-bg) !important;
}
.nav-search__dropdown .pagefind-ui__result:hover {
background-color: var(--color-bg-accent) !important;
}
.nav-search__dropdown .pagefind-ui__result-link {
color: var(--color-text) !important;
font-weight: var(--font-bold) !important;
}
.nav-search__dropdown .pagefind-ui__result-excerpt {
color: var(--color-text-accent) !important;
}
.nav-search__dropdown .pagefind-ui__message {
background-color: var(--color-bg) !important;
color: var(--color-text-accent) !important;
}
.nav-search__view-all {
display: block;
padding: var(--space-xs) var(--space-s);
margin-block-start: var(--space-xs);
text-align: center;
font-size: var(--size-step--1);
color: var(--color-primary);
text-decoration: none;
border-top: 1px solid var(--color-bg-accent);
}
.nav-search__view-all:hover {
background-color: var(--color-bg-accent);
}
.nav-search__view-all[hidden] {
display: none;
}
{%- endcss -%}
{%- js "defer" -%}
(function() {
const trigger = document.querySelector('.nav-search__trigger');
const dropdown = document.getElementById('nav-search-dropdown');
const viewAllLink = document.getElementById('nav-search-view-all');
if (!trigger || !dropdown) return;
let pagefindLoaded = false;
trigger.addEventListener('click', function(e) {
e.preventDefault();
const expanded = trigger.getAttribute('aria-expanded') === 'true';
trigger.setAttribute('aria-expanded', !expanded);
dropdown.hidden = expanded;
if (!expanded && !pagefindLoaded && typeof PagefindUI !== 'undefined') {
new PagefindUI({
element: "#nav-search-container",
showSubResults: false,
showImages: false,
excerptLength: 10,
pageSize: 5,
placeholder: "Search companies..."
});
pagefindLoaded = true;
// Watch for input changes to update "View all" link
setTimeout(() => {
const input = dropdown.querySelector('input');
if (input) {
input.focus();
input.addEventListener('input', function() {
if (this.value.trim()) {
viewAllLink.href = '/search/?q=' + encodeURIComponent(this.value);
viewAllLink.hidden = false;
} else {
viewAllLink.hidden = true;
}
});
}
}, 100);
}
});
// Close on click outside
document.addEventListener('click', function(e) {
if (!e.target.closest('.nav-search')) {
trigger.setAttribute('aria-expanded', 'false');
dropdown.hidden = true;
}
});
// Close on escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && !dropdown.hidden) {
trigger.setAttribute('aria-expanded', 'false');
dropdown.hidden = true;
trigger.focus();
}
});
})();
{%- endjs -%}
================================================
FILE: src/_includes/partials/pagination.njk
================================================
<section class="region">
<nav role="navigation" aria-labelledby="pagination_label">
<span id="pagination_label" hidden>{{ meta[metaKey].paginationLabel }}</span>
<span id="prefix" hidden>{{ meta[metaKey].paginationPage }}</span>
<ol class="pagination | cluster" role="list">
<li>
{% if pagination.href.previous %}
<a href="{{ pagination.href.previous }}"> {{ meta[metaKey].paginationPrevious }} </a>
{% else %}
{{ meta[metaKey].paginationPrevious }}
{% endif %}
</li>
{% if meta[metaKey].paginationNumbers %}
{%- for pageEntry in pagination.pages -%}
<li>
<a
href="{{ pagination.hrefs[ loop.index0 ] }}"
id="link_{{ loop.index }}"
aria-labelledby="prefix link_{{ loop.index }}"
{% if page.url == pagination.hrefs[ loop.index0 ] %}aria-current="page"{% endif %}
>
{{ loop.index }}</a
>
</li>
{%- endfor -%}
{% endif %}
<li>
{% if pagination.href.next %}
<a href="{{ pagination.href.next }}">{{ meta[metaKey].paginationNext }}</a>
{% else %}
{{ meta[metaKey].paginationNext }}
{% endif %}
</li>
</ol>
</nav>
</section>
{% css "local" %}
{% include "css/pagination.css" %}
{% endcss %}
================================================
FILE: src/_includes/partials/theme-switch.njk
================================================
<is-land on:idle
><div
role="region"
class="theme-switch | cluster"
style="--cluster-horizontal-alignment: center; --gutter: 0.25rem"
aria-labelledby="theme-switcher-label"
data-theme-switcher
>
<h2 id="theme-switcher-label" class="visually-hidden">{{ meta.themeSwitch.title }}</h2>
<button class="button" id="light-theme-toggle" data-theme="light" data-ghost-button data-small-button>
<span>{{ meta.themeSwitch.light }}</span>
</button>
<button class="button" id="dark-theme-toggle" data-theme="dark" data-ghost-button data-small-button>
<span>{{ meta.themeSwitch.dark }}</span>
</button>
</div>
</is-land>
================================================
FILE: src/_includes/schemas/BlogPosting.njk
================================================
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"mainEntityOfPage": {
"@type": "WebPage"
},
"isPartOf": {
"@id": "{{ meta.url }}#website"
},
"@id": "{{ url }}",
"headline": "{{ title or meta.siteName }}",
"description": "{{ discover.description or description or meta.siteDescription }}",
"image": "{{ meta.url }}{% if discover.image %}/assets/images/{{ discover.image }}{% else %}{{ meta.opengraph_default }}{% endif %}",
"inLanguage": "{{ meta.locale }}",
"publisher": {
"@type": "{{ meta.siteType }}",
"name": "{{ meta.author.name }}",
"url": "{{ meta.url }}"
},
"author": {
"@type": "Person",
"name": "{{ meta.author.name }}"
},
"datePublished": "{{ date | toIsoString }}"
}
</script>
================================================
FILE: src/_includes/schemas/BreadcrumbList.njk
================================================
{# Generate breadcrumbs based on page URL - only for pages with paths #}
{%- set cleanUrl = page.url | trim('/') -%}
{%- if cleanUrl -%}
{%- set urlParts = cleanUrl | split('/') -%}
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "{{ meta.url }}/"
}
{%- set position = 2 -%}
{%- set currentPath = "" -%}
{%- for part in urlParts -%}
{%- set currentPath = currentPath + "/" + part -%}
{# Determine label #}
{%- if loop.last and title -%}
{%- set label = title -%}
{%- elif part == "companies" -%}
{%- set label = "Companies" -%}
{%- elif part == "browse" -%}
{%- set label = "Browse" -%}
{%- elif part == "blog" -%}
{%- set label = "Blog" -%}
{%- elif part == "about" -%}
{%- set label = "About" -%}
{%- elif part == "tags" -%}
{%- set label = "Tags" -%}
{%- else -%}
{%- set label = part | replace("-", " ") | title -%}
{%- endif -%}
,{
"@type": "ListItem",
"position": {{ position }},
"name": "{{ label | replace('"', '\\"') }}",
"item": "{{ meta.url }}{{ currentPath }}/"
}
{%- set position = position + 1 -%}
{%- endfor -%}
]
}
</script>
{%- endif -%}
================================================
FILE: src/_includes/schemas/Organization.njk
================================================
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "{{ title }}",
"url": "{{ website }}"
{%- if careers_url %},
"sameAs": ["{{ careers_url }}"]
{%- endif %}
}
</script>
================================================
FILE: src/_includes/schemas/WebSite.njk
================================================
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebSite",
"@id": "{{ meta.url }}#website",
"url": "{{ meta.url }}",
"name": "{{ meta.siteName }}",
"description": "{{ meta.siteDescription }}",
"inLanguage": "{{ meta.locale }}",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "{{ meta.url }}/companies/?q={search_term_string}"
},
"query-input": "required name=search_term_string"
}
}
]
}
</script>
================================================
FILE: src/_includes/webc/custom-card.webc
================================================
<custom-card class="flow no-indicator" webc:root>
<slot name="image"></slot>
<slot name="headline"></slot>
<div class="meta | cluster"><slot name="date"></slot><slot name="tag"></slot></div>
<slot name="content"></slot>
<slot name="footer"></slot>
</custom-card>
================================================
FILE: src/_includes/webc/custom-masonry.webc
================================================
<is-land on:visible>
<custom-masonry class="grid" data-rows="masonry" :data-layout="layout" webc:root webc:keep>
<slot></slot>
<template data-island="once" webc:raw>
<script src="/assets/scripts/components/custom-masonry.js" type="module"></script>
</template>
</custom-masonry>
</is-land>
================================================
FILE: src/_includes/webc/custom-peertube-link.webc
================================================
<style>
custom-peertube-link {
display: flex;
align-items: flex-start;
gap: var(--space-xs);
font-size: var(--size-step-min-1);
}
custom-peertube-link svg {
font-size: var(--size-step-0);
margin-block-start: 0.1em;
}
</style>
<custom-svg aria-hidden="true" src="./src/assets/svg/platform/peertube.svg"></custom-svg>
<a :href="href"><span webc:if="label" @text="label" webc:nokeep></span></a>
<!-- Inspired by https://github.com/zachleat/zachleat.com -->
================================================
FILE: src/_includes/webc/custom-peertube.webc
================================================
<script webc:setup>
function getLinkUrl(instance, slug, start) {
return `https://${instance}/w/${slug}${start ? `?start=${start}s` : ''}`;
}
function getEmbedLinkUrl(instance, embedSlug, start) {
const startParam = start ? `start=${start}s&` : '';
return `https://${instance}/videos/embed/${embedSlug}?${startParam}title=0&warningTitle=0`;
}
</script>
<style>
/* Hide without JS */
is-land:not(:defined).video-wrapper {
display: none;
}
is-land.video-wrapper {
display: block;
}
custom-peertube iframe {
position: absolute;
inset: 0px;
width: 100%;
height: 100%;
}
custom-peertube .video-wrapper {
overflow: hidden;
position: relative;
aspect-ratio: 16 / 9;
width: 100%;
border-radius: 0.2em;
}
</style>
<custom-peertube webc:root class="flow">
<is-land on:visible class="video-wrapper relative">
<iframe
:title="label"
:src="getEmbedLinkUrl(instance,embedSlug,start)"
frameborder="0"
allowfullscreen
sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
>
</iframe>
</is-land>
</custom-peertube>
<custom-peertube-link
@raw="label"
:label="label"
:href="getLinkUrl(instance,slug, start)"
></custom-peertube-link>
================================================
FILE: src/_includes/webc/custom-svg.webc
================================================
<script webc:type="render" webc:is="template">
const Image = require('@11ty/eleventy-img');
const {optimize} = require('svgo');
const slugify = require('slugify');
module.exports = async function () {
const meta = await Image(this.src, {
formats: ['svg'],
dryRun: true
});
const plugins = [];
const excludeAttributes = ['src', 'uid'];
const svgContents = meta.svg[0].buffer.toString();
const getAttributes = () => {
const arr = [];
for (const prop in this.webc.attributes) {
if (!excludeAttributes.includes(prop)) {
const attribute = slugify(prop, {lower: true, strict: true}); // Use slugify directly here
const value = this.webc.attributes[prop];
arr.push({[attribute]: value});
}
}
return arr;
};
if (this.webc.attributes) {
plugins.push({
name: 'addAttributesToSVGElement',
params: {
attributes: getAttributes()
}
});
}
const result = optimize(svgContents, {plugins});
return result.data;
};
</script>
================================================
FILE: src/_includes/webc/custom-youtube-link.webc
================================================
<style>
custom-youtube-link {
display: flex;
align-items: flex-start;
gap: var(--space-xs);
font-size: var(--size-step-min-1);
}
custom-youtube-link svg {
font-size: var(--size-step-0);
margin-block-start: 0.1em;
}
</style>
<custom-svg aria-hidden="true" src="./src/assets/svg/platform/youtube.svg"></custom-svg>
<a :href="href"><span webc:if="label" @text="label" webc:nokeep></span></a>
<!-- Inspired by https://github.com/zachleat/zachleat.com -->
================================================
FILE: src/_includes/webc/custom-youtube.webc
================================================
<!-- component composition by: https://github.com/zachleat/zachleat.com -->
<script webc:setup>
function getPosterImage(slug, posterSize = 'auto') {
let videoUrl = `https://youtube.com/watch?v=${slug}`;
return `https://v1.opengraph.11ty.dev/${encodeURIComponent(videoUrl)}/${posterSize}/jpeg/`;
}
function getLinkUrl(slug, start) {
return `https://youtube.com/watch?v=${slug}${start ? `&t=${start}s` : ''}`;
}
</script>
<style>
/* Hide without JS */
is-land:not(:defined).video-wrapper {
display: none;
}
</style>
<custom-youtube webc:root class="flow">
<is-land on:visible class="video-wrapper">
<lite-youtube
:videoid="slug"
:js-api="jsapi !== 'undefined' ? '' : false"
:params="start ? `start=${start}` : false"
:playlabel="`Play${label ? `: ${label}` : ''}`"
:style="`background-image: var(--yt-poster-img-url);--yt-poster-img-url-lazy: url('${poster || getPosterImage(slug, posterSize)}');`"
></lite-youtube>
<template data-island="once" webc:raw>
<style>
lite-youtube {
max-inline-size: 100% !important;
background-size: cover;
}
is-land lite-youtube {
background-color: #eee;
border-radius: 0.2em;
background-size: cover;
}
is-land[ready] lite-youtube {
/* gotta set in `style` to override the 480w image from lite-youtube */
--yt-poster-img-url: var(--yt-poster-img-url-lazy);
}
.video-wrapper {
aspect-ratio: 16 / 9;
width: 100%;
}
is-land.video-wrapper {
display: block;
}
</style>
<link rel="stylesheet" href="/assets/components/lite-yt-embed.css" />
<script type="module" src="/assets/components/lite-yt-embed.js"></script>
</template>
</is-land>
</custom-youtube>
<custom-youtube-link @raw="label" :label="label" :href="getLinkUrl(slug, start)"></custom-youtube-link>
================================================
FILE: src/_layouts/base.njk
================================================
{% set assetHash = helpers.random() %}
<!doctype html>
<html lang="{{ meta.lang }}">
<!-- The order of elements in the head follows recommendations by Harry Roberts. learn more here: https://www.youtube.com/watch?v=MHyAOZ45vnU -->
<head>
<!-- 1 charset/viewport -->
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- 2 title -->
<title>
{%- if title -%}
{{- title -}}
{%- else -%}
{{- meta.siteName -}}
{%- endif -%}
</title>
<base href="{{ page.url }}" />
<!-- 3 synchronous js -->
{% include "head/js-inline.njk" %}
<!-- 4 schema settings -->
{% include "head/schema.njk" %}
<!-- 5 CSS -->
<!-- <link rel="stylesheet" href="/assets/css/global.css?{{ assetHash }}" /> -->
{% include "head/css-inline.njk" %}
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<!-- 6 preloads -->
{% include "head/preloads.njk" %}
<!-- 7 defer -->
{% include "head/js-defer.njk" %}
<script src="/pagefind/pagefind-ui.js"></script>
<!-- 8 meta tags, icons, open graph etc. -->
{% include "head/meta-info.njk" %}
<!-- 9 Analytics (production only) -->
{%- if meta.analytics.fathom.siteId and eleventy.env.runMode == "build" -%}
<script src="https://cdn.usefathom.com/script.js" data-site="{{ meta.analytics.fathom.siteId }}" defer></script>
<script src="/assets/scripts/outbound-tracking.js" defer></script>
{%- endif -%}
</head>
<body class="{{ layout }}">
{% set indicateActiveHome %}
{% if page.url == "/" %}
aria-current=page
{% endif %}
{% endset %}
{% include "partials/header.njk" %}
<main id="main" class="flow">{{ content | safe }}</main>
{% include "partials/footer.njk" %}
</body>
</html>
================================================
FILE: src/_layouts/company.njk
================================================
---
layout: base
schema: Organization
---
<article class="company-profile | region wrapper" data-pagefind-body>
<header class="company-profile__header flow">
<a href="/companies/" class="company-profile__back">← All Companies</a>
<h1 class="gradient-text">{{ title }}</h1>
<div class="company-profile__meta | cluster gutter-s">
{% set buttonUrl = careers_url or website %}
{% if buttonUrl %}
<a href="{{ buttonUrl }}" target="_blank" rel="noopener noreferrer" class="button" data-button-variant="primary" data-track-outbound="{{ title }}">
{% if careers_url %}Apply Now{% else %}Visit Website{% endif %} →
</a>
{% endif %}
{% if region %}
<a href="/browse/{{ region }}/" class="tag tag--region">{{ labels.region[region] or region }}</a>
{% endif %}
{% if remote_policy %}
<a href="/browse/{{ remote_policy }}/" class="tag tag--policy">{{ labels.remotePolicy[remote_policy] or remote_policy }}</a>
{% endif %}
</div>
</header>
<div class="company-profile__content prose flow">
{{ content | safe }}
{% if technologies and technologies.length > 0 %}
<section class="company-profile__tech">
<h2>Tech Stack</h2>
<div class="cluster gutter-2xs">
{% for tech in technologies %}
<a href="/browse/{{ tech }}/" class="tag tag--tech">{{ labels.tech[tech] or tech }}</a>
{% endfor %}
</div>
</section>
{% endif %}
</div>
<footer class="company-profile__footer">
<div class="cluster gutter-s">
<a href="/companies/" class="button" data-button-variant="outline">
← Back to All Companies
</a>
<a href="https://github.com/remoteintech/remote-jobs/edit/main/src/companies/{{ page.fileSlug }}.md"
class="button"
data-button-variant="secondary"
target="_blank"
rel="noopener noreferrer">
Edit on GitHub
</a>
</div>
{% if updatedAt %}
<span class="company-profile__updated">Last updated: {{ updatedAt | formatDate('MMMM D, YYYY') }}</span>
{% endif %}
</footer>
</article>
{%- css "local" -%}
.company-profile__header {
margin-block-end: var(--space-l);
}
.company-profile__back {
font-size: var(--size-step--1);
color: var(--color-text-accent);
text-decoration: none;
}
.company-profile__back:hover {
color: var(--color-primary);
}
.company-profile__meta {
margin-block-start: var(--space-m);
flex-wrap: wrap;
align-items: center;
}
.tag {
display: inline-block;
font-size: var(--size-step--1);
padding: 0.2em 0.6em;
border-radius: 4px;
white-space: nowrap;
text-decoration: none;
transition: opacity var(--transition-duration) var(--transition-timing),
transform var(--transition-duration) var(--transition-timing);
}
.tag:hover {
opacity: 0.85;
transform: translateY(-1px);
}
.tag--region {
background-color: var(--color-highlight);
color: var(--color-dark);
}
.tag--policy {
background-color: var(--color-secondary);
color: var(--color-light);
}
.tag--tech {
background-color: var(--color-bg-accent);
color: var(--color-text);
border: 1px solid var(--color-bg-accent-2);
}
.tag--tech:hover {
border-color: var(--color-primary);
opacity: 1;
}
.company-profile__content {
--flow-space: var(--space-m-l);
}
.company-profile__content h2 {
margin-top: var(--space-l-xl);
color: var(--color-primary);
font-size: var(--size-step-2);
padding-block-end: var(--space-2xs);
border-block-end: 2px solid var(--color-bg-accent);
}
.company-profile__content ul {
padding-inline-start: var(--space-m);
}
.company-profile__content li {
margin-block-end: var(--space-2xs);
}
.company-profile__tech {
margin-block-start: var(--space-l);
}
.company-profile__footer {
margin-block-start: var(--space-xl);
padding-block-start: var(--space-l);
border-block-start: 1px solid var(--color-bg-accent);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: var(--space-s);
}
.company-profile__updated {
font-size: var(--size-step--1);
color: var(--color-text-accent);
}
{%- endcss -%}
================================================
FILE: src/_layouts/page.njk
================================================
---
layout: base
---
<div class="region wrapper flow prose" style="--region-space-top: var(--space-xl-2xl)">
<h1 class="gradient-text">{{ title }}</h1>
{{ content | safe }}
</div>
================================================
FILE: src/_layouts/post.njk
================================================
---
layout: base
schema: BlogPosting
---
<div class="region" style="--region-space-top: var(--space-xl-2xl)">
<div class="wrapper flow prose">
<h1 class="gradient-text">{{ title }}</h1>
{% set imgPath = image %}
{% if imgPath and imgPath | fileExists %}
{% image imgPath, alt or "", credit, "eager", "feature" %}
{% endif %}
<p class="meta | cluster gutter-xs-s">
<!-- draft status -->
{%- if draft -%}
<span class="button" data-small-button data-button-variant="tertiary">draft</span>
{%- endif %}
<!-- date -->
{% set definedDate = date %} {% include "partials/date.njk" %}
{%
if tags.length >
1
%}
<!-- tags -->
{% for tag in tags %}{% if tag != "posts" %}
<a class="button" href="/tags/{{ tag | slugify }}/" data-small-button> {{ tag }} </a>
{% endif %}{% endfor %}
{% endif %}
</p>
{{ content | safe }}
</div>
<!-- h-card infos: https://indieweb.org/authorship -->
<div hidden class="h-entry">
<a class="u-url" href="{{ page.url | url | absoluteUrl(meta.url) }}">{{ title }}</a>
<a class="p-name u-url" rel="author" href="{{ meta.url }}">{{ meta.author.name }}</a>
<img
eleventy:ignore
class="u-author h-card"
src="{{ meta.author.avatar | url | absoluteUrl(meta.url) }}"
alt="{{ meta.author.name }}"
/>
</div>
</div>
{%- css "local" -%}
{%- include 'css/post.css' -%}
{%- include 'css/footnotes.css' -%}
{%- endcss -%}
================================================
FILE: src/_layouts/tags.njk
================================================
---
layout: base
---
<div class="region">
<div class="wrapper flow">
<h1 class="gradient-text-linear text-step-4">{{ title }}</h1>
{{ content | safe }}
</div>
</div>
================================================
FILE: src/assets/css/global/base/fonts.css
================================================
@font-face {
font-family: 'Atkinson Hyperlegible';
font-style: normal;
font-display: swap;
font-weight: 400;
src:
local(''),
url('/assets/fonts/atkinson/atkinson-hyperlegible-regular.woff2') format('woff2');
}
@font-face {
font-family: 'Atkinson Hyperlegible';
font-style: italic;
font-display: swap;
font-weight: 400;
src:
local(''),
url('/assets/fonts/atkinson/atkinson-hyperlegible-italic.woff2') format('woff2');
}
@font-face {
font-family: 'Atkinson Hyperlegible';
font-style: normal;
font-display: swap;
font-weight: 700;
src:
local(''),
url('/assets/fonts/atkinson/atkinson-hyperlegible-bold.woff2') format('woff2');
}
@font-face {
font-family: 'Redhat';
font-style: normal;
font-display: swap;
font-weight: 900;
src:
local(''),
url('/assets/fonts/redhat/red-hat-display-v7-latin-900.woff2') format('woff2');
}
================================================
FILE: src/assets/css/global/base/global-styles.css
================================================
/*
Global styles
Low-specificity, global styles that apply to the whole
project: https://cube.fyi/css.html
*/
html {
color-scheme: light dark;
}
body {
display: flex;
flex-direction: column;
font-family: var(--font-base);
font-size: var(--size-step-0);
font-weight: var(--font-regular);
font-size-adjust: from-font;
line-height: var(--leading-standard);
color: var(--color-text);
background-color: var(--color-bg);
accent-color: var(--color-primary);
letter-spacing: var(--tracking);
}
main {
flex: auto;
}
h1,
h2,
h3 {
font-family: var(--font-display);
font-weight: var(--font-extra-bold);
line-height: var(--leading-fine);
letter-spacing: var(--tracking-s);
}
h1 {
font-size: var(--size-step-6);
}
h2 {
font-size: var(--size-step-4);
}
h3 {
font-size: var(--size-step-2);
}
blockquote {
padding: var(--space-m-l);
border-inline-start: 0.5rem solid var(--color-primary);
font-size: var(--size-step-2);
}
blockquote > * + * {
margin-block-start: var(--space-m-l);
}
blockquote :last-child {
font-family: var(--font-base);
font-style: normal;
font-size: var(--size-step-1);
}
input,
textarea {
caret-color: var(--color-primary);
}
svg {
block-size: 0.6lh;
inline-size: auto;
flex: none;
}
b,
strong {
font-weight: var(--font-bold);
}
hr {
border: none;
height: 1px;
width: 10%;
margin-block: var(--flow-space, var(--space-m-l));
margin-inline-start: 0;
background-color: var(--color-bg-accent-2);
}
figcaption {
margin-block-start: var(--space-s);
font-size: var(--size-step-min-1);
text-align: center;
padding-block-end: var(--space-xs);
}
figcaption:after {
border-block: var(--stroke);
content: '';
display: block;
margin-block: var(--space-xs);
/* block-size: 1rem; */
inline-size: 50%;
margin-inline: auto;
opacity: 0.8;
}
a {
text-decoration-thickness: 0.15ex;
text-underline-offset: 0.2ch;
}
a:not([class]):hover {
text-decoration: none;
}
:focus {
outline: none;
}
:focus-visible {
outline: 3px solid var(--focus-color, currentColor);
outline-offset: var(--focus-offset, 0.3ch);
}
/* reduce focus offset for Firefox
*/
@supports (-moz-appearance: none) {
:root {
--focus-offset: 0.08em;
}
}
::selection {
background-color: var(--color-text);
color: var(--color-bg);
}
@media (scripting: none) {
.require-js {
display: none;
}
}
================================================
FILE: src/assets/css/global/base/reset.css
================================================
/* https://piccalil.li/blog/a-modern-css-reset/ */
/* https://keithjgrant.com/posts/2024/01/my-css-resets/ */
/* https://moderncss.dev/modern-css-for-dynamic-component-based-architecture/#css-reset-additions */
/* https://github.com/mayank99/reset.css/blob/main/package/index.css */
*,
*::before,
*::after {
box-sizing: border-box;
}
* {
text-wrap: pretty;
}
h1,
h2,
h3,
h4 {
text-wrap: balance;
}
html:focus-within {
scroll-behavior: smooth;
}
body {
min-height: 100vh;
min-height: 100dvh;
text-rendering: optimizeSpeed;
line-height: 1.5;
}
body,
h1,
h2,
h3,
h4,
p,
figure,
blockquote,
dl,
dd {
margin: 0;
}
ul[role='list'],
ol[role='list'] {
list-style: none;
}
[role='list'] {
padding: 0;
}
a {
text-decoration-skip-ink: auto;
color: currentColor;
}
img,
picture,
svg,
canvas {
max-inline-size: 100%;
block-size: auto;
vertical-align: middle;
font-style: italic;
background-repeat: no-repeat;
background-size: cover;
shape-margin: 0.75rem;
}
picture {
display: block;
}
button {
all: unset;
}
button,
input,
select,
textarea {
font: inherit;
color: inherit;
}
textarea {
resize: vertical;
resize: block;
}
textarea:not([rows]) {
min-height: 10em;
}
button,
label,
select,
summary,
[role='button'],
[role='option'] {
cursor: pointer;
}
:target {
scroll-margin-block-start: 2ex;
}
:focus {
scroll-margin-block-end: 8vh;
}
dialog {
border: none;
background: none;
inset: unset;
max-width: unset;
max-height: unset;
}
[popover] {
border: none;
background: none;
inset: unset;
color: inherit;
}
dialog:not([open], [popover]),
[popover]:not(:popover-open) {
display: none !important;
}
html:has(dialog[open]:modal) {
overflow: hidden;
}
@media (prefers-reduced-motion: reduce) {
:focus-within {
scroll-behavior: auto;
}
*,
::after,
::before {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: 0.01ms !important;
}
}
================================================
FILE: src/assets/css/global/base/variables.css
================================================
/* Global variables. */
/* Basic variable definitions for color schemes */
:root {
--gutter: var(--space-m-l);
--transition-duration: 250ms;
--transition-timing: ease;
--wrapper-width: 85rem;
--tracking: -0.04ch;
--tracking-s: -0.075ch;
--tracking-wide: 0.04ch;
--stroke: 1px solid var(--color-bg-accent);
--gradient-rainbow: linear-gradient(90deg, #6366F1 10%, #14B8A6 30%, #22D3EE 50%, #818CF8 75%, #6366F1 90%);
--gradient-conic: conic-gradient(
var(--color-primary) 0 28%,
var(--color-secondary) 0 67%,
var(--color-tertiary) 0 100%
);
--gradient-stripes: linear-gradient(
45deg,
var(--color-gray-900) 0 75%,
var(--color-primary) 0 85%,
var(--color-secondary) 0 92%,
var(--color-tertiary) 0 100%
);
--gradient-brand: linear-gradient(135deg, var(--color-indigo) 0%, var(--color-teal) 100%);
--color-light: #F5F7FF;
--color-dark: #090E1A;
--color-mid: var(--color-gray-400);
}
/* Light theme */
:root,
:root[data-theme='light'] {
--color-text: var(--color-gray-800);
--color-bg: var(--color-light);
--color-primary: var(--color-indigo);
--color-secondary: var(--color-teal);
--color-tertiary: var(--color-cyan);
--color-highlight: var(--color-periwinkle);
--color-text-accent: var(--color-gray-600);
--color-bg-accent: var(--color-gray-200);
--color-bg-accent-2: var(--color-gray-300);
}
/* Dark theme */
@media (prefers-color-scheme: dark) {
:root {
--color-text: var(--color-gray-100);
--color-bg: var(--color-dark);
--color-primary: var(--color-periwinkle);
--color-secondary: var(--color-teal-subdued);
--color-tertiary: var(--color-cyan-subdued);
--color-highlight: var(--color-indigo-subdued);
--color-text-accent: var(--color-gray-300);
--color-bg-accent: var(--color-gray-800);
--color-bg-accent-2: var(--color-gray-700);
}
}
:root[data-theme='dark'] {
--color-text: var(--color-gray-100);
--color-bg: var(--color-dark);
--color-primary: var(--color-periwinkle);
--color-secondary: var(--color-teal-subdued);
--color-tertiary: var(--color-cyan-subdued);
--color-highlight: var(--color-indigo-subdued);
--color-text-accent: var(--color-gray-300);
--color-bg-accent: var(--color-gray-800);
--color-bg-accent-2: var(--color-gray-700);
}
================================================
FILE: src/assets/css/global/blocks/button.css
================================================
/* based on Andy Bell's article: https://piccalil.li/blog/how-i-build-a-button-component/ */
.button {
--button-bg: var(--color-text);
--button-color: color-mix(in oklab, var(--button-bg) 10%, var(--color-bg));
--button-hover-bg: color-mix(in oklab, var(--button-bg) 90%, var(--color-bg));
--button-border-width: var(--border-thickness);
--button-border-style: solid;
--button-border-color: color-mix(in oklab, var(--button-bg) 80%, var(--color-text));
--button-radius: var(--border-radius-small);
--button-gap: var(--space-2xs);
--button-padding: var(--space-xs) var(--space-m);
--button-font-family: var(--font-body);
--button-font-weight: var(--font-regular);
--button-font-size: var(--size-step-0);
--button-text-transform: none;
--button-tracking: normal;
display: inline-flex;
align-items: center;
gap: var(--button-gap);
padding: var(--button-padding);
background: var(--button-bg);
color: var(--button-color);
border-width: var(--button-border-width);
border-style: var(--button-border-style);
border-color: var(--button-border-color);
border-radius: var(--button-radius);
text-decoration: none;
font-family: var(--button-font-family);
font-weight: var(--button-font-weight);
font-size: var(--button-font-size);
line-height: var(--leading-flat);
text-transform: var(--button-text-transform);
letter-spacing: var(--button-tracking);
/* trim the space at the cap height - in Safari Technology Preview */
text-box-trim: trim-both;
text-box-edge: cap alphabetic;
}
.button svg {
block-size: var(--button-icon-size, 1.2cap);
}
/* Hover/focus/active */
.button:hover,
.button[aria-current='page'],
.button[aria-pressed='true'],
.button[data-state='active'] {
background: var(--button-hover-bg);
color: var(--button-color);
}
.button:focus {
outline-color: var(--button-outline-color, var(--button-border-color));
}
.button:active {
transform: scale(99%);
}
/* Variants */
.button[data-button-variant='primary'] {
--button-bg: var(--color-primary);
--button-color: var(--color-light);
--button-color: color-mix(in oklab, var(--color-primary) 5%, var(--color-light));
}
.button[data-button-variant='secondary'] {
--button-bg: var(--color-secondary);
--button-color: var(--color-light);
--button-color: color-mix(in oklab, var(--color-secondary) 5%, var(--color-light));
}
.button[data-button-variant='tertiary'] {
--button-bg: var(--color-tertiary);
--button-color: var(--color-dark);
--button-color: color-mix(in oklab, var(--color-tertiary) 10%, var(--color-dark));
}
.button[data-button-variant='outline'] {
--button-bg: transparent;
--button-border-color: var(--color-primary);
--button-color: var(--color-primary);
--button-hover-bg: var(--color-primary);
}
.button[data-button-variant='outline']:hover {
--button-color: var(--color-light);
}
.button[data-ghost-button] {
--button-bg: var(--color-bg);
--button-border-color: var(--color-text);
--button-color: var(--color-text);
--button-hover-color: var(--color-bg);
}
.button[data-ghost-button]:hover {
--_ghost-hover-bg: var(--color-bg);
--_ghost-hover-bg: color-mix(in oklab, var(--button-bg) 95%, var(--color-dark));
background: var(--_ghost-hover-bg);
color: var(--button-color);
}
.button[data-small-button] {
--button-border-width: 2px;
--button-radius: var(--border-radius-small);
--button-font-size: var(--size-step-min-2);
--button-padding: var(--space-2xs) var(--space-s) var(--space-3xs) var(--space-s);
--button-text-transform: uppercase;
--button-tracking: var(--tracking-wide);
}
/* Radius variants */
.button[data-button-radius='hard'] {
--button-radius: 0;
}
================================================
FILE: src/assets/css/global/blocks/code.css
================================================
:root {
--color-code-orange: hsl(30, 70%, 60%);
--color-code-blue: var(--color-secondary);
--color-code-indigo: hsl(260, 48%, 56%);
--color-code-violet: hsl(314, 70%, 60%);
--color-code-pink: hsl(350, 70%, 60%);
--color-code-gray: hsl(0, 0%, 58%);
--color-code-bg: color-mix(in oklab, var(--color-bg) 92%, black);
}
code,
pre {
font-family: var(--font-mono);
font-size: var(--size-step-min-1);
background-color: var(--color-code-bg);
border-radius: var(--border-radius-medium);
}
code[class*='language-'],
pre[class*='language-'] {
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
hyphens: none;
}
/* Specific Styles for <pre> Elements */
pre {
grid-column: popout !important;
overflow-x: auto;
padding: var(--space-s) var(--space-l);
}
/* Style Adjustments for <code> within different contexts */
:where(:not(pre)) > code {
position: relative;
top: -0.05em;
padding: 0.1em 0.4em;
}
pre[class*='language-'] {
overflow: auto;
position: relative;
}
[class*='language-'] .namespace {
opacity: 0.7;
}
.token.atrule {
color: var(--color-code-pink);
}
.token.attr-name {
color: var(--color-code-orange);
}
.token.attr-value {
color: var(--color-text-accent);
}
.token.attribute {
color: var(--color-code-blue);
}
.token.boolean {
color: var(--color-code-pink);
}
.token.builtin {
color: var(--color-code-orange);
}
.token.cdata {
color: var(--color-code-orange);
}
.token.char {
color: var(--color-code-orange);
}
.token.class {
color: var(--color-code-orange);
}
.token.class-name {
color: var(--color-code-orange);
}
.token.color {
color: var(--color-code-orange);
}
.token.comment {
color: var(--color-code-gray);
}
.token.constant {
color: var(--color-code-pink);
}
.token.deleted {
color: var(--color-code-pink);
}
.token.doctype {
color: var(--color-code-orange);
}
.token.entity {
color: var(--color-code-pink);
}
.token.function {
color: var(--color-code-pink);
}
.token.hexcode {
color: var(--color-code-orange);
}
.token.id {
color: var(--color-code-pink);
font-weight: var(--font-bold);
}
.token.important {
color: var(--color-code-pink);
font-weight: var(--font-bold);
}
.token.inserted {
color: var(--color-code-orange);
}
.token.keyword {
color: var(--color-code-pink);
font-style: italic;
}
.token.number {
color: var(--color-text-accent);
}
.token.operator {
color: var(--color-code-gray);
}
.token.prolog {
color: var(--color-code-orange);
}
.token.property {
color: var(--color-code-orange);
}
.token.pseudo-class {
color: var(--color-code-blue);
}
.token.pseudo-element {
color: var(--color-code-blue);
}
.token.punctuation {
color: var(--color-code-gray);
}
.token.regex {
color: var(--color-code-orange);
}
.token.selector {
color: var(--color-code-pink);
}
.token.string {
color: var(--color-text-accent);
}
.token.symbol {
color: var(--color-code-pink);
}
.token.tag {
color: var(--color-code-pink);
}
.token.unit {
color: var(--color-code-pink);
}
.token.url {
color: var(--color-code-violet);
}
.token.variable {
color: var(--color-code-pink);
}
/* CodePen iframe */
.codepen a {
--icon-size: 1.2em;
display: flex;
align-items: center;
gap: var(--space-2xs);
}
.prose .cp_embed_wrapper,
.prose .cp_embed_wrapper + script + *:not(h2) {
--flow-space: var(--space-l);
}
.cp_embed_wrapper {
grid-column: popout;
position: relative;
overflow: auto;
display: grid;
place-items: center;
grid-template-areas: 'container';
resize: horizontal;
}
.cp_embed_wrapper iframe {
grid-area: container;
width: 100%;
}
================================================
FILE: src/assets/css/global/blocks/company-card.css
================================================
/* Company Card Component */
.company-card {
background-color: var(--color-bg);
border: 2px solid var(--color-bg-accent);
border-radius: var(--border-radius-medium);
padding: var(--space-s-m);
transition: border-color var(--transition-duration) var(--transition-timing),
box-shadow var(--transition-duration) var(--transition-timing);
position: relative;
}
.company-card:hover,
.company-card:focus-within {
border-color: var(--color-primary);
box-shadow: 0 4px 12px color-mix(in oklab, var(--color-primary) 15%, transparent);
}
.company-card__name {
font-size: var(--size-step-1);
font-weight: var(--font-bold);
margin-block-end: var(--space-2xs);
line-height: var(--leading-fine);
}
.company-card__name a {
text-decoration: none;
color: var(--color-text);
}
/* Make entire card clickable via name link */
.company-card__name a::after {
content: '';
position: absolute;
inset: 0;
}
.company-card__region {
font-size: var(--size-step--1);
color: var(--color-text-accent);
margin-block-end: var(--space-2xs);
line-height: var(--leading-standard);
}
.company-card__website {
font-size: var(--size-step--1);
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.company-card__website a {
color: var(--color-secondary);
text-decoration: none;
position: relative;
z-index: 1; /* Above the card link overlay */
}
.company-card__website a:hover {
text-decoration: underline;
}
/* Prevent external link indicator on card website links */
.company-card__website a::after {
display: none !important;
}
/* Grid layout for card collections */
.company-cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
gap: var(--space-m);
}
================================================
FILE: src/assets/css/global/blocks/external-link.css
================================================
a[href^='http']:not([href*='localhost:8080']):not([href*='dougaitken.co.uk']):not(.button):not(
.no-indicator
):not(.external-link),
.indicator {
padding-inline-end: 0.8em;
}
a[href^='http']:not([href*='localhost:8080']):not([href*='dougaitken.co.uk']):not(.button):not(
.no-indicator
):not(.external-link)::after,
.indicator::after {
position: absolute;
display: inline-block;
inline-size: 1em;
block-size: 1em;
background-image: url('/assets/images/template/external.svg');
background-repeat: no-repeat;
background-position: center;
background-size: 60% auto;
/* alternative text rules */
content: '(external link)';
overflow: hidden;
white-space: nowrap;
text-indent: 1em; /* the width of the icon */
}
/* Ensure buttons never get external link indicators */
.button::after {
display: none !important;
content: none !important;
}
================================================
FILE: src/assets/css/global/blocks/main-nav.css
================================================
.mainnav {
--nav-list-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55);
position: var(--nav-position, absolute);
inset-inline-end: 0;
}
.mainnav:has([data-drawer-toggle][aria-expanded='true']) {
--nav-position: fixed;
inset-inline-end: var(--gap);
}
.mainnav ul {
/* configuration */
--gutter: var(--space-xs);
--cluster-vertical-alignment: normal;
--cluster-wrap: wrap;
--cluster-direction: column;
--nav-list-background: var(--color-bg);
--nav-list-shadow: -5px 0 11px 0 hsl(0 0% 0% / 0.2);
--nav-list-height: 100dvh;
--nav-list-padding-block: var(--space-2xl);
--nav-list-padding-inline: var(--space-s);
--nav-list-position: fixed;
--nav-list-width: min(18rem, 100vw);
--nav-list-visibility: hidden;
--nav-list-opacity: 0;
background: var(--nav-list-background);
box-shadow: var(--nav-list-shadow);
block-size: var(--nav-list-height);
list-style: none;
margin: 0;
padding-block: var(--nav-list-padding-block);
padding-inline: var(--nav-list-padding-inline);
position: var(--nav-list-position);
inset-block-start: 0;
inset-inline-end: 0;
inline-size: var(--nav-list-width);
opacity: var(--nav-list-opacity);
transition:
opacity 0.3 var(--nav-list-timing-function),
visibility 0.3s ease-in-out;
visibility: var(--nav-list-visibility);
}
.mainnav ul[no-flash] {
transition: none;
}
@media (prefers-reduced-motion: no-preference) {
.mainnav > ul {
--nav-list-transform: translateX(100%);
--nav-list-opacity: 1;
transform: var(--nav-list-transform);
transition:
transform 0.5s var(--nav-list-timing-function),
visibility 0.3s linear;
}
.mainnav svg {
transition: transform 0.4s var(--nav-list-timing-function);
}
}
.mainnav [data-drawer-toggle] {
--gutter: var(--space-2xs);
--cluster-vertical-alignment: center;
background: var(--color-bg);
display: var(--nav-button-display, inline-flex);
position: relative;
z-index: 2;
padding: var(--space-2xs) 0;
line-height: var(--leading-flat);
}
.mainnav [data-drawer-toggle][aria-expanded='true'] + ul {
--cluster-wrap: nowrap;
--nav-list-visibility: visible;
--nav-list-transform: translateX(0);
--nav-list-opacity: 1;
overflow-y: auto;
}
body:has([data-drawer-toggle][aria-expanded='true']) {
overflow: hidden;
}
.mainnav span {
font-weight: var(--font-bold);
text-transform: uppercase;
font-family: var(--font-display);
font-size: var(--size-step-min-1);
}
.mainnav svg {
block-size: 0.9em;
color: var(--color-text);
stroke-width: 3;
}
.mainnav [aria-expanded='true'] svg {
transform: rotate(45deg);
}
.mainnav :is(a, [data-submenu-toggle]) {
/* configuration */
--nav-item-background: transparent;
--nav-item-text-color: var(--color-text);
--nav-item-padding: var(--space-xs) var(--space-2xs);
--nav-item-decoration-color: transparent;
--nav-item-font-size: var(--size-step-0);
--nav-item-font-weight: var(--font-bold);
background: var(--nav-item-background);
color: var(--nav-item-text-color);
font-size: var(--nav-item-font-size);
padding: var(--nav-item-padding);
display: block;
border-radius: var(--border-radius-medium);
text-decoration-line: underline;
text-decoration-color: var(--nav-item-decoration-color);
text-decoration-thickness: 3px;
text-underline-offset: 0.2em;
}
.mainnav:has(.nav-sublist) :is(a, [data-submenu-toggle]) {
font-weight: var(--nav-item-font-weight);
}
.mainnav [data-submenu-toggle] {
gap: var(--space-2xs);
display: flex;
inline-size: 100%;
align-items: center;
justify-content: space-between;
}
.mainnav [data-submenu-toggle] svg {
margin-inline-end: calc(var(--gap) - var(--nav-list-padding-inline));
}
.mainnav :is(a, [data-submenu-toggle]):hover {
--nav-item-background: transparent;
--nav-item-text-color: var(--color-text);
--nav-item-decoration-color: var(--color-bg-accent-2);
}
.mainnav [aria-current='page'],
.mainnav [data-state='active'] {
--nav-item-background: var(--color-bg);
--nav-item-text-color: var(--color-primary);
--nav-item-decoration-color: var(--color-primary);
}
/* current parent (if submenu) */
li:has(ul a[aria-current='page']) > [data-submenu-toggle] {
--nav-item-background: var(--color-bg);
--nav-item-text-color: var(--color-text);
--nav-item-decoration-color: var(--color-text);
}
/* sub menu */
.mainnav [data-submenu-toggle][aria-expanded='false'] + ul {
display: none;
}
.mainnav .nav-sublist {
--gutter: 0;
--cluster-direction: column;
--nav-sublist-position: relative;
--nav-sublist-background: var(--color-bg);
--nav-sublist-width: 100%;
--nav-list-visibility: visible;
--nav-list-opacity: 1;
--nav-list-padding-block: 0 var(--space-m);
--nav-list-padding-inline: 0;
box-shadow: none;
position: var(--nav-sublist-position);
inline-size: var(--nav-sublist-width);
block-size: auto;
background: var(--nav-sublist-background);
z-index: 2;
}
.mainnav .nav-sublist a {
--nav-item-font-weight: var(--font-regular);
}
@media screen(navigation) {
.mainnav {
--nav-position: static;
--nav-button-display: none;
}
.mainnav :is(a, [data-submenu-toggle]) {
--nav-item-font-weight: var(--font-regular);
}
.mainnav ul {
--cluster-direction: row;
--nav-list-position: static;
--nav-list-padding-block: 0;
--nav-list-padding-inline: 0;
--nav-list-height: auto;
--nav-list-width: 100%;
--nav-list-shadow: none;
--nav-list-visibility: visible;
--nav-list-transform: translateX(0);
--nav-list-opacity: 1;
}
.mainnav [aria-current='page'],
.mainnav [data-state='active'],
li:has(ul a[aria-current='page']) > [data-submenu-toggle] {
--nav-item-background: transparent;
--nav-item-text-color: var(--color-primary);
--nav-item-decoration-color: var(--color-primary);
}
.mainnav [data-submenu-toggle] {
inline-size: auto;
}
.mainnav [data-submenu-toggle] svg {
margin-inline-end: 0;
}
.mainnav .nav-sublist {
--nav-sublist-position: absolute;
--nav-sublist-background: var(--color-bg);
--nav-sublist-border: var(--color-text);
--nav-sublist-width: max-content;
--nav-list-padding-block: var(--space-xs);
--nav-list-padding-inline: var(--space-xs);
border: 3px solid var(--nav-sublist-border);
inset-block-start: var(--space-xl);
inset-inline-start: var(--space-2xs);
}
}
/* Repeat the settings to provide a different styling when JavaScript is disabled or drawerNav is set to false. The selector
assumes that the button doesn’t exist without JS, making the list the first child within the navigation. */
.mainnav ul:first-child {
--cluster-direction: row;
--nav-list-position: static;
--nav-list-padding-block: 0;
--nav-list-padding-inline: 0;
--nav-list-height: auto;
--nav-list-width: 100%;
--nav-list-shadow: none;
--nav-list-visibility: visible;
--nav-list-transform: translateX(0);
--nav-list-opacity: 1;
}
.mainnav ul:first-child [aria-current='page'],
.mainnav ul:first-child [data-state='active'] {
--nav-item-background: transparent;
--nav-item-text-color: var(--color-primary);
--nav-item-decoration-color: var(--color-primary);
}
/* make menu wrap without drawer */
.mainnav:has(ul:first-child) {
--nav-position: relative;
}
/* no JS fallback for sub menus */
@media (scripting: none) {
.mainnav ul:first-child ul.nav-sublist {
--cluster-direction: row;
--cluster-wrap: wrap;
}
}
@media (scripting: none) {
@media screen(navigation) {
.mainnav ul:first-child ul.nav-sublist {
--cluster-direction: column;
}
}
}
================================================
FILE: src/assets/css/global/blocks/prose.css
================================================
/* Based on Andy Bell, https://github.com/Andy-set-studio/personal-site-eleventy */
.prose {
--flow-space: var(--space-m-l);
--wrapper-width: 64rem;
}
.prose :is(pre, pre + *, figure, picture) {
--flow-space: var(--space-m-l);
}
.prose :is(figure + *, picture + *) {
--flow-space: var(--space-xl);
}
.prose :is(img, video) {
border: var(--stroke);
}
.prose :is(h2, h3, h4) {
--flow-space: 1.5em;
overflow-wrap: anywhere;
hyphens: auto;
}
@media screen(md) {
.prose :is(h1, h2, h3) {
overflow-wrap: unset;
hyphens: unset;
}
}
.prose :is(h1, h2, h3, h4) + *:not([class]):not(figure) {
--flow-space: var(--space-l);
}
.prose :is(p, li, dl, blockquote) {
max-inline-size: 60ch;
text-wrap: pretty;
}
.prose .heading-anchor:where(:hover, :focus) {
text-decoration: none;
}
.heading-anchor {
text-decoration: none;
}
.prose mark {
background-color: var(--color-tertiary);
color: var(--color-dark);
}
/* block space only for "regular lists" and sublists */
.prose :is(ul, ol):not([class]) li + li,
.prose :is(ul, ol):not([class]) li > :is(ol, ul) {
padding-block-start: var(--space-s);
}
/* marker only for "regular lists" */
.prose :is(ul:not([class]):not([role='list'])) li::marker {
color: var(--color-primary);
content: '– ';
}
.prose ul:not([class]) {
padding-inline-start: 1.3ch;
}
.prose ol:not([class]) {
padding-inline-start: 1.7ch;
}
.prose ol li::marker {
color: var(--color-primary);
}
.prose img {
border-radius: var(--border-radius-medium);
}
@media screen(ltsm) {
.prose > *,
.prose a {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
/* Adds a hyphen where the word breaks, if supported (No Blink) */
hyphens: auto;
}
}
================================================
FILE: src/assets/css/global/blocks/section.css
================================================
.section > .divider:first-child {
transform: rotate(180deg) translateY(-1px);
}
.section__inner {
background-color: var(--spot-color, var(--color-bg-accent));
color: var(--color-text);
}
.section blockquote {
font-weight: var(--font-bold);
line-height: 1;
font-size: var(--size-step-4);
letter-spacing: var(--tracking-s);
}
.section :is(h1, h2, h3, blockquote) {
opacity: 95%;
}
================================================
FILE: src/assets/css/global/blocks/seperator.css
================================================
.divider {
display: block;
block-size: 3.5em;
inline-size: 100%;
fill: var(--spot-color, var(--color-bg));
}
.divider > .divider {
transform: translateY(-1px);
}
================================================
FILE: src/assets/css/global/blocks/site-footer.css
================================================
.site-footer {
--cluster-horizontal-alignment: center;
}
/* increase tab size */
.site-footer a:not(.creator a) {
padding: var(--space-xs);
}
.site-footer .creator a:hover {
color: transparent;
background-image: var(--gradient-rainbow);
background-size: 100%;
background-repeat: repeat;
background-clip: text;
}
.site-footer .creator a:hover svg {
color: var(--color-primary);
}
================================================
FILE: src/assets/css/global/blocks/site-logo.css
================================================
.logo {
--gutter: var(--space-xs);
padding: var(--space-s) 0;
font-size: var(--size-step-0);
text-decoration: none;
}
================================================
FILE: src/assets/css/global/blocks/skip-link.css
================================================
.skip-link {
clip: rect(1px, 1px, 1px, 1px);
display: block;
block-size: 1px;
overflow: hidden;
position: absolute;
inline-size: 1px;
top: 1rem;
left: 1rem;
z-index: 999;
}
.skip-link:focus {
clip: auto;
block-size: auto;
overflow: visible;
inline-size: auto;
background-color: var(--color-text);
color: var(--color-bg);
padding: var(--space-xs) var(--space-s-m);
border-radius: var(--border-radius-medium);
line-height: 1;
}
.skip-link:not(:focus) {
border: 0;
clip: rect(0 0 0 0);
block-size: auto;
margin: 0;
overflow: hidden;
padding: 0;
position: absolute;
inline-size: 1px;
white-space: nowrap;
}
================================================
FILE: src/assets/css/global/blocks/textgradient.css
================================================
.gradient-text {
color: transparent;
background-image: var(--gradient-conic);
padding: 0.6rem 0;
background-size: 50%;
background-clip: text;
}
.gradient-text-linear {
color: transparent;
background-image: var(--gradient-rainbow);
background-size: 100%;
background-repeat: repeat;
background-clip: text;
}
================================================
FILE: src/assets/css/global/blocks/theme-switch.css
================================================
.theme-switch h2 {
font-size: var(--size-step-min-1);
font-family: var(--font-base);
}
.theme-switch .button[aria-pressed='true'] {
--_color-contrast: var(--color-tertiary);
--button-bg: var(--_color-contrast);
--button-color: var(--color-dark);
--button-border-color: var(--_color-contrast);
outline-color: var(--_color-contrast);
}
/* Hide without JS */
is-land:not(:defined) .theme-switch {
display: none;
}
================================================
FILE: src/assets/css/global/compositions/cluster.css
================================================
/*
CLUSTER
More info: https://every-layout.dev/layouts/cluster/
A layout that lets you distribute items with consistent
spacing, regardless of their size
CUSTOM PROPERTIES AND CONFIGURATION
--gutter (var(--space-s-m)): This defines the space
between each item.
--cluster-horizontal-alignment (flex-start) How items should align
horizontally. Can be any acceptable flexbox alignment value.
--cluster-vertical-alignment How items should align vertically.
Can be any acceptable flexbox alignment value.
*/
.cluster {
display: flex;
flex-direction: var(--cluster-direction, row);
flex-wrap: var(--cluster-wrap, wrap);
gap: var(--gutter, var(--space-s-l));
justify-content: var(--cluster-horizontal-alignment, flex-start);
align-items: var(--cluster-vertical-alignment, center);
}
================================================
FILE: src/assets/css/global/compositions/flow.css
================================================
/*
FLOW UTILITY
Like the Every Layout stack: https://every-layout.dev/layouts/stack/
Info about this implementation: https://piccalil.li/quick-tip/flow-utility/
*/
.flow > * + * {
margin-block-start: var(--flow-space, 1em);
}
================================================
FILE: src/assets/css/global/compositions/grid.css
================================================
/* AUTO GRID
Related Every Layout: https://every-layout.dev/layouts/grid/
More info on the flexible nature: https://piccalil.li/tutorial/create-a-responsive-grid-layout-with-no-media-queries-using-css-grid/
A flexible layout that will create an auto-fill grid with
configurable grid item sizes
CUSTOM PROPERTIES AND CONFIGURATION
--gutter (var(--space-s-m)): This defines the space
between each item.
--grid-min-item-size (14rem): How large each item should be
ideally, as a minimum.
--grid-placement (auto-fill): Set either auto-fit or auto-fill
to change how empty grid tracks are handled */
.grid {
display: grid;
grid-template-columns: repeat(
var(--grid-placement, auto-fill),
minmax(var(--grid-min-item-size, 16rem), 1fr)
);
gap: var(--gutter, var(--space-s-m));
}
.grid[data-rows='masonry'] {
grid-template-rows: masonry;
align-items: start;
}
.grid[data-layout='50-50'] {
--grid-placement: auto-fit;
--grid-min-item-size: clamp(16rem, 50vw, 28rem);
}
.grid[data-layout='thirds'] {
--grid-placement: auto-fit;
--grid-min-item-size: clamp(16rem, 33%, 20rem);
}
================================================
FILE: src/assets/css/global/compositions/repel.css
================================================
/*
REPEL
A little layout that pushes items away from each other where
there is space in the viewport and stacks on small viewports
CUSTOM PROPERTIES AND CONFIGURATION
--gutter (var(--space-s-m)): This defines the space
between each item.
--repel-vertical-alignment How items should align vertically.
Can be any acceptable flexbox alignment value.
*/
.repel {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: var(--repel-vertical-alignment, center);
gap: var(--gutter, var(--space-s-l));
}
.repel[data-nowrap] {
flex-wrap: nowrap;
}
================================================
FILE: src/assets/css/global/compositions/sidebar.css
================================================
/*
SIDEBAR
More info: https://every-layout.dev/layouts/sidebar/
A layout that allows you to have a flexible main content area
and a "fixed" width sidebar that sits on the left or right.
If there is not enough viewport space to fit both the sidebar
width *and* the main content minimum width, they will stack
on top of each other
CUSTOM PROPERTIES AND CONFIGURATION
--gutter (var(--space-size-1)): This defines the space
between the sidebar and main content.
--sidebar-target-width (20rem): How large the sidebar should be
--sidebar-content-min-width(50%): The minimum size of the main content area
EXCEPTIONS
.sidebar[data-direction='rtl']: flips the sidebar to be on the right
*/
.sidebar {
display: flex;
flex-wrap: wrap;
gap: var(--gutter, var(--space-s-l));
}
.sidebar:not([data-direction]) > :first-child {
flex-basis: var(--sidebar-target-width, 20rem);
flex-grow: 1;
}
.sidebar:not([data-direction]) > :last-child {
flex-basis: 0;
flex-grow: 999;
min-inline-size: var(--sidebar-content-min-width, 50%);
}
/*
A flipped version where the sidebar is on the right
*/
.sidebar[data-direction='rtl'] > :last-child {
flex-basis: var(--sidebar-target-width, 20rem);
flex-grow: 1;
}
.sidebar[data-direction='rtl'] > :first-child {
flex-basis: 0;
flex-grow: 999;
min-inline-size: var(--sidebar-content-min-width, 50%);
}
================================================
FILE: src/assets/css/global/compositions/wrapper.css
================================================
/* © Ryan Mulligan - https://ryanmulligan.dev/blog/layout-breakouts/ */
.wrapper {
--gap: clamp(1rem, 6vw, 3rem);
--full: minmax(var(--gap), 1fr);
--content: min(var(--wrapper-width, 85rem), 100% - var(--gap) * 2);
--popout: minmax(0, 2rem);
--feature: minmax(0, 5rem);
display: grid;
grid-template-columns:
[full-start] var(--full)
[feature-start] var(--feature)
[popout-start] var(--popout)
[content-start] var(--content) [content-end]
var(--popout) [popout-end]
var(--feature) [feature-end]
var(--full) [full-end];
}
.wrapper > * {
grid-column: content;
}
.prose-wrapper {
--wrapper-width: 64rem;
}
.popout {
grid-column: popout;
}
.feature {
grid-column: feature;
}
.full {
grid-column: full;
}
================================================
FILE: src/assets/css/global/global.css
================================================
@import 'tailwindcss/base' layer(tailwindBase);
@import 'base/reset.css' layer(reset);
@import 'base/fonts.css' layer(fonts);
@import 'tailwindcss/components' layer(tailwindComponents);
@import 'base/variables.css' layer(variables);
@import 'base/global-styles.css' layer(global);
@import-glob 'compositions/*.css' layer(compositions);
@import-glob 'blocks/*.css' layer(blocks);
@import-glob 'utilities/*.css' layer(utilities);
/* debugging */
/* @import-glob 'tests/*.css' layer(test); */
@import 'tailwindcss/utilities' layer(tailwindUtilities);
================================================
FILE: src/assets/css/global/tests/debug.css
================================================
/* https://heydonworks.com/article/testing-html-with-modern-css/ */
/* WIP */
:root {
--highlight-outline: 0.25rem solid cornflowerblue;
--warning-outline: 0.25rem solid orange;
--error-outline: 0.25rem solid red;
}
/* outline all elements */
* * * * * {
outline: var(--highlight-outline);
}
/* highlight empty elements */
*:empty:not(svg *) {
outline: var(--warning-outline);
--warning-empty-element: 'The element is empty';
}
a:not([href]) {
outline: var(--error-outline);
--error-not-href: 'The link does not have an href. Did you mean to use a <button>?';
}
a[href^='javascript'] {
outline: var(--error-outline);
--error-javascript-href: 'The href does not appear to include a location. Did you mean to use a <button>?';
}
a[disabled] {
outline: var(--error-outline);
--error-anchor-disabled: 'The disabled property is not valid on anchors (links). Did you mean to use a <button>?';
}
label:not(:has(:is(input, output, textarea, select))):not([for]) {
outline: var(--error-outline);
--error-unassociated-label: 'The <label> neither uses the `for` attribute nor wraps an applicable form element';
}
input:not(form input) {
outline: var(--warning-outline);
--warning-input-orphan: 'The input is outside a <form> element. Users may benefit from <form> semantics and behaviors.';
}
figure[aria-label]:not(:has(figcaption)) {
outline: var(--warning-outline);
--warning-figure-label-not-visible: 'The labeling method used is not visible and only available to assistive software';
}
figure[aria-label] figcaption {
outline: var(--warning-outline);
--warning-overridden-figcaption: 'The figure has a figcaption that is overridden by an ARIA label';
}
ol[class*='breadcrumb']:not(:is(nav[aria-label], nav[aria-labelledby]) ol) {
outline: var(--error-outline);
--error-undiscoverable-breadcrumbs: 'It looks like you have provided breadcrumb navigation outside a labeled `<nav>` landmark';
}
#body
:not(:is(header, nav, main, aside, footer)):not(:is(header, nav, main, aside, footer) *):not(.skip-link) {
outline: var(--error-outline);
--error-content-outside-landmark: 'You have some content that is not inside a landmark (header, nav, main, aside, or footer)';
}
header nav:has(ul > ul) {
outline: var(--warning-outline);
--warning-nested-navigation: 'You appear to be using tiered/nested navigation in your header. This can be difficult to traverse. Index pages with tables of content are preferable.';
}
================================================
FILE: src/assets/css/global/tests/is-land.css
================================================
:root {
--island-ready: 0.25rem solid ForestGreen;
--island-component: 0.25rem solid goldenrod;
}
is-land[ready] {
display: block;
background-color: rgba(114, 233, 110, 0.2);
outline: var(--island-outline);
}
is-land,
.demo-component {
display: block;
padding: 0.5em;
margin: 0.5em 0;
outline: var(--island-component);
}
================================================
FILE: src/assets/css/global/utilities/grayscale.css
================================================
.grayscale {
filter: grayscale(100%);
}
================================================
FILE: src/assets/css/global/utilities/heading-line.css
================================================
.heading-line {
display: flex;
align-items: flex-start;
text-align: left;
}
.heading-line::after {
content: '';
flex-grow: 1;
block-size: 1px;
background: var(--color-bg-accent-2);
margin-block-start: auto;
transform: translateY(-0.25em);
margin-left: var(--space-s);
}
================================================
FILE: src/assets/css/global/utilities/ontop.css
================================================
/* set on parent div to get the right z-index context */
.ontop {
z-index: 1;
position: relative;
}
================================================
FILE: src/assets/css/global/utilities/region.css
================================================
/**
* REGION
* Add consistent vertical padding to create regions of content
* Can either be configured by setting --region-space or use a default from the space scale
*/
.region {
--region-space-fallback: var(--region-space, var(--space-l-xl));
padding-block-start: var(--region-space-top, var(--region-space-fallback));
padding-block-end: var(--region-space-bottom, var(--region-space-fallback));
}
================================================
FILE: src/assets/css/global/utilities/spin.css
================================================
.spin {
animation: spin 30s linear infinite;
}
@media (prefers-reduced-motion: no-preference) {
@keyframes spin {
0% {
transform: rotate(0deg);
}
to {
transform: rotate(1turn);
}
}
}
================================================
FILE: src/assets/css/global/utilities/visually-hidden.css
================================================
/*
VISUALLY HIDDEN UTILITY
Info: https://piccalil.li/quick-tip/visually-hidden/
*/
.visually-hidden {
border: 0;
clip: rect(0 0 0 0);
height: 0;
margin: 0;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
white-space: nowrap;
}
================================================
FILE: src/assets/css/local/custom-card.css
================================================
custom-card {
--gutter: var(--space-xs-s);
background-color: var(--card-bg, var(--color-bg-accent));
border: 4px solid var(--color-bg-accent);
color: var(--color-text);
padding: var(--space-s-m);
border-radius: var(--border-radius-medium);
max-inline-size: unset;
display: grid;
grid-template-rows: [image] max-content [headline] max-content [meta] max-content [desc] auto [footer] max-content;
}
/* avoid flow space being added to unused elements with nested slots */
custom-card > :empty {
display: none;
}
custom-card ::selection {
color: var(--color-dark);
background-color: var(--color-secondary);
}
custom-card :is(h2, h3) {
--flow-space: var(--space-m);
grid-row: headline;
}
custom-card :is(h2, h3) a {
text-decoration: none;
}
custom-card > :is(picture, figure) {
grid-row: image;
--flow-space: 0;
}
custom-card img {
max-inline-size: var(--max-img-width, 100%);
block-size: auto;
aspect-ratio: 16/9;
object-fit: cover;
object-position: center;
}
custom-card > .meta {
grid-row: meta;
font-size: var(--size-step-min-1);
}
custom-card > p {
grid-row: desc;
}
custom-card > footer {
grid-row: footer;
font-size: var(--size-step-min-2);
}
/* avoid overflow of long words */
custom-card :is(a, p, h2, h3) {
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
}
/* -------------------- variants -------------------- */
/* no padding */
custom-card[no-padding] {
background-color: transparent;
border: 4px solid var(--color-bg-accent);
padding: 0;
gitextract_lztpi08t/ ├── .eleventyignore ├── .env-sample ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ └── new_company.md │ ├── PULL_REQUEST_TEMPLATE.MD │ ├── dependabot.yml │ ├── scripts/ │ │ └── validate-companies.js │ └── workflows/ │ ├── ci.yml │ ├── codeql-analysis.yml │ └── validate-companies.yml ├── .gitignore ├── .nojekyll ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── CLAUDE.md ├── LICENSE ├── README.md ├── eleventy.config.js ├── package.json ├── src/ │ ├── _config/ │ │ ├── collections.js │ │ ├── events/ │ │ │ ├── build-css.js │ │ │ ├── build-js.js │ │ │ └── svg-to-jpeg.js │ │ ├── events.js │ │ ├── filters/ │ │ │ ├── dates.js │ │ │ ├── fileExists.js │ │ │ ├── markdown-format.js │ │ │ ├── slugify.js │ │ │ ├── sort-alphabetic.js │ │ │ ├── sort-random.js │ │ │ ├── split.js │ │ │ ├── splitlines.js │ │ │ └── striptags.js │ │ ├── filters.js │ │ ├── plugins/ │ │ │ ├── drafts.js │ │ │ ├── html-config.js │ │ │ └── markdown.js │ │ ├── plugins.js │ │ ├── setup/ │ │ │ ├── create-colors.js │ │ │ └── generate-favicons.js │ │ ├── shortcodes/ │ │ │ ├── image.js │ │ │ └── svg.js │ │ ├── shortcodes.js │ │ └── utils/ │ │ ├── clamp-generator.js │ │ └── tokens-to-tailwind.js │ ├── _data/ │ │ ├── changelog.json │ │ ├── companyHelpers.js │ │ ├── designTokens/ │ │ │ ├── borderRadius.json │ │ │ ├── colors.json │ │ │ ├── colorsBase.json │ │ │ ├── fonts.json │ │ │ ├── spacing.json │ │ │ ├── textLeading.json │ │ │ ├── textSizes.json │ │ │ ├── textWeights.json │ │ │ └── viewports.json │ │ ├── github.js │ │ ├── helpers.js │ │ ├── labels.js │ │ ├── meta.js │ │ ├── navigation.js │ │ └── personal.js │ ├── _includes/ │ │ ├── head/ │ │ │ ├── css-inline.njk │ │ │ ├── js-defer.njk │ │ │ ├── js-inline.njk │ │ │ ├── meta-info.njk │ │ │ ├── preloads.njk │ │ │ └── schema.njk │ │ ├── partials/ │ │ │ ├── card-tag.njk │ │ │ ├── company-card.njk │ │ │ ├── date.njk │ │ │ ├── details.njk │ │ │ ├── edit-on.njk │ │ │ ├── footer.njk │ │ │ ├── header.njk │ │ │ ├── main-nav.njk │ │ │ ├── nav-search.njk │ │ │ ├── pagination.njk │ │ │ └── theme-switch.njk │ │ ├── schemas/ │ │ │ ├── BlogPosting.njk │ │ │ ├── BreadcrumbList.njk │ │ │ ├── Organization.njk │ │ │ └── WebSite.njk │ │ └── webc/ │ │ ├── custom-card.webc │ │ ├── custom-masonry.webc │ │ ├── custom-peertube-link.webc │ │ ├── custom-peertube.webc │ │ ├── custom-svg.webc │ │ ├── custom-youtube-link.webc │ │ └── custom-youtube.webc │ ├── _layouts/ │ │ ├── base.njk │ │ ├── company.njk │ │ ├── page.njk │ │ ├── post.njk │ │ └── tags.njk │ ├── assets/ │ │ ├── css/ │ │ │ ├── global/ │ │ │ │ ├── base/ │ │ │ │ │ ├── fonts.css │ │ │ │ │ ├── global-styles.css │ │ │ │ │ ├── reset.css │ │ │ │ │ └── variables.css │ │ │ │ ├── blocks/ │ │ │ │ │ ├── button.css │ │ │ │ │ ├── code.css │ │ │ │ │ ├── company-card.css │ │ │ │ │ ├── external-link.css │ │ │ │ │ ├── main-nav.css │ │ │ │ │ ├── prose.css │ │ │ │ │ ├── section.css │ │ │ │ │ ├── seperator.css │ │ │ │ │ ├── site-footer.css │ │ │ │ │ ├── site-logo.css │ │ │ │ │ ├── skip-link.css │ │ │ │ │ ├── textgradient.css │ │ │ │ │ └── theme-switch.css │ │ │ │ ├── compositions/ │ │ │ │ │ ├── cluster.css │ │ │ │ │ ├── flow.css │ │ │ │ │ ├── grid.css │ │ │ │ │ ├── repel.css │ │ │ │ │ ├── sidebar.css │ │ │ │ │ └── wrapper.css │ │ │ │ ├── global.css │ │ │ │ ├── tests/ │ │ │ │ │ ├── debug.css │ │ │ │ │ └── is-land.css │ │ │ │ └── utilities/ │ │ │ │ ├── grayscale.css │ │ │ │ ├── heading-line.css │ │ │ │ ├── ontop.css │ │ │ │ ├── region.css │ │ │ │ ├── spin.css │ │ │ │ └── visually-hidden.css │ │ │ └── local/ │ │ │ ├── custom-card.css │ │ │ ├── details.css │ │ │ ├── footnotes.css │ │ │ ├── forms.css │ │ │ ├── gallery.css │ │ │ ├── nav-main-drawer-cls.css │ │ │ ├── pagination.css │ │ │ ├── post.css │ │ │ ├── styleguide.css │ │ │ └── table.css │ │ └── scripts/ │ │ ├── bundle/ │ │ │ ├── details.js │ │ │ ├── dialog.js │ │ │ ├── is-land.js │ │ │ ├── nav-drawer.js │ │ │ ├── nav-sub.js │ │ │ └── theme-toggle.js │ │ ├── components/ │ │ │ └── custom-masonry.js │ │ └── outbound-tracking.js │ ├── blog/ │ │ ├── 2017-10-03-announcing-remote-in-tech-company.md │ │ ├── 2017-10-09-so-is-this-just-another-job-board.md │ │ ├── 2017-12-06-popular-company-profiles-and-the-github-traffic-charts.md │ │ ├── 2017-12-28-highest-view-count.md │ │ ├── 2018-01-01-new-year-new-job.md │ │ ├── 2018-01-06-advice-from-a-constant-job-seeker.md │ │ ├── 2018-09-19-website-updates.md │ │ ├── 2018-09-25-hacktoberfest-is-back.md │ │ ├── 2026-01-13-the-big-redesign.md │ │ ├── 2026-01-17-seo-improvements.md │ │ ├── 2026-02-15-keeping-things-tidy.md │ │ └── blog.json │ ├── common/ │ │ ├── 404.njk │ │ ├── _redirects.njk │ │ ├── carbon.njk │ │ ├── feed-atom.njk │ │ ├── feed-json.njk │ │ ├── humans.njk │ │ ├── og-images.njk │ │ ├── robots.njk │ │ ├── site-manifest.njk │ │ ├── sitemap.njk │ │ ├── tagList.njk │ │ └── tags.njk │ ├── companies/ │ │ ├── 10up.md │ │ ├── 15five.md │ │ ├── 1password.md │ │ ├── 37signals.md │ │ ├── 3blocks.md │ │ ├── 42-technologies.md │ │ ├── 90-seconds.md │ │ ├── a-1-auto-transport.md │ │ ├── abiturma.md │ │ ├── ably.md │ │ ├── abstract.md │ │ ├── acquia.md │ │ ├── activecampaign.md │ │ ├── ad-hoc.md │ │ ├── adaface.md │ │ ├── addstructure.md │ │ ├── adeva.md │ │ ├── adzuna.md │ │ ├── aerolab.md │ │ ├── aerostrat.md │ │ ├── aestudio.md │ │ ├── aha.md │ │ ├── aiir.md │ │ ├── aim-india.md │ │ ├── airbyte.md │ │ ├── airgarage.md │ │ ├── airtreks.md │ │ ├── aivitex.md │ │ ├── akamai.md │ │ ├── akka.md │ │ ├── alami.md │ │ ├── alan.md │ │ ├── algorand.md │ │ ├── algorithmia.md │ │ ├── alice.md │ │ ├── alight-solutions.md │ │ ├── alley.md │ │ ├── allydvm.md │ │ ├── alphasights.md │ │ ├── amazon.md │ │ ├── ambaum.md │ │ ├── andela.md │ │ ├── animalz.md │ │ ├── annertech.md │ │ ├── anomali.md │ │ ├── apartment-therapy.md │ │ ├── appcues.md │ │ ├── appen.md │ │ ├── appinio.md │ │ ├── applaudo.md │ │ ├── applied-ai-company-aaico.md │ │ ├── appstractor.md │ │ ├── appwrite.md │ │ ├── argyle.md │ │ ├── arkency.md │ │ ├── art-and-logic.md │ │ ├── artefactual.md │ │ ├── articulate.md │ │ ├── asana.md │ │ ├── astronomer.md │ │ ├── atento.md │ │ ├── atlassian.md │ │ ├── atozdebug.md │ │ ├── audiense.md │ │ ├── aula.md │ │ ├── auth0.md │ │ ├── automattic.md │ │ ├── axelerant.md │ │ ├── axios.md │ │ ├── bairesdev.md │ │ ├── balena.md │ │ ├── balsamiq.md │ │ ├── bandcamp.md │ │ ├── bandlab.md │ │ ├── bandzoogle.md │ │ ├── baremetrics.md │ │ ├── basecamp.md │ │ ├── bear-group.md │ │ ├── bebanjo.md │ │ ├── beenverified.md │ │ ├── best-practical-solutions.md │ │ ├── betable.md │ │ ├── betapeak.md │ │ ├── betterup.md │ │ ├── beyond-company.md │ │ ├── beyondpricing.md │ │ ├── bharatpe.md │ │ ├── big-cartel.md │ │ ├── bill.md │ │ ├── bilstein-group.md │ │ ├── bit-zesty.md │ │ ├── bitnami.md │ │ ├── bitovi.md │ │ ├── bizink.md │ │ ├── blameless.md │ │ ├── bloc.md │ │ ├── bluecat-networks.md │ │ ├── bluespark.md │ │ ├── boldare.md │ │ ├── bonsai.md │ │ ├── bounteous.md │ │ ├── brainstorm-force.md │ │ ├── bright-funds.md │ │ ├── brikl.md │ │ ├── britecore.md │ │ ├── broadwing.md │ │ ├── buffer.md │ │ ├── bugfender.md │ │ ├── buysellads.md │ │ ├── cabify.md │ │ ├── calamari.md │ │ ├── calendly.md │ │ ├── calibre.md │ │ ├── cancom.md │ │ ├── canonical.md │ │ ├── capchase.md │ │ ├── capgemini.md │ │ ├── capital-one.md │ │ ├── capital-placement.md │ │ ├── capmo.md │ │ ├── carbon-black.md │ │ ├── cards-against-humanity.md │ │ ├── carecru.md │ │ ├── caremessage.md │ │ ├── carmatec.md │ │ ├── cartodb.md │ │ ├── cartstack.md │ │ ├── casumo.md │ │ ├── chainlink.md │ │ ├── chargify.md │ │ ├── charity-water.md │ │ ├── chatgen.md │ │ ├── checkly.md │ │ ├── chef.md │ │ ├── chess.md │ │ ├── chroma.md │ │ ├── circleci.md │ │ ├── circonus.md │ │ ├── civicactions.md │ │ ├── civo.md │ │ ├── clevertech.md │ │ ├── clickup.md │ │ ├── clootrack.md │ │ ├── close.md │ │ ├── cloudapp.md │ │ ├── cloudera.md │ │ ├── cloudlinux.md │ │ ├── coalition-technologies.md │ │ ├── code-b-solutions.md │ │ ├── code-like-a-girl.md │ │ ├── codea-it.md │ │ ├── codepen.md │ │ ├── codesandbox.md │ │ ├── codeship.md │ │ ├── codingcops.md │ │ ├── cofense.md │ │ ├── coforma.md │ │ ├── cognizant.md │ │ ├── coinbase.md │ │ ├── coingape.md │ │ ├── collabora.md │ │ ├── comet.md │ │ ├── companies.11tydata.js │ │ ├── companies.json │ │ ├── compucorp.md │ │ ├── connexa.md │ │ ├── consensys.md │ │ ├── consumer-financial-protection-bureau.md │ │ ├── continu.md │ │ ├── conversio.md │ │ ├── convert.md │ │ ├── coodesh.md │ │ ├── core-apps.md │ │ ├── coreos.md │ │ ├── corgibytes.md │ │ ├── cosmic-chimps.md │ │ ├── coursera.md │ │ ├── cratedb.md │ │ ├── crazygames.md │ │ ├── creex-team.md │ │ ├── crossover.md │ │ ├── crowdstrike.md │ │ ├── cueup.md │ │ ├── customer-io.md │ │ ├── cuvette.md │ │ ├── cvs-health.md │ │ ├── cwt.md │ │ ├── cyber-whale.md │ │ ├── daktronics.md │ │ ├── dappradar.md │ │ ├── darecode.md │ │ ├── dashboardhub.md │ │ ├── dashlane.md │ │ ├── data-science-brigade.md │ │ ├── data-science-dojo.md │ │ ├── datacamp.md │ │ ├── datadog.md │ │ ├── datastax.md │ │ ├── dave.md │ │ ├── dealdash.md │ │ ├── deel.md │ │ ├── delighted.md │ │ ├── designcode.md │ │ ├── deskpass.md │ │ ├── dev-spotlight.md │ │ ├── devsquad.md │ │ ├── dgraph.md │ │ ├── digitalocean.md │ │ ├── discord.md │ │ ├── discourse.md │ │ ├── dnsimple.md │ │ ├── docker.md │ │ ├── doist.md │ │ ├── doit.md │ │ ├── dow-jones.md │ │ ├── dronedeploy.md │ │ ├── dropbox.md │ │ ├── drupal-jedi.md │ │ ├── duckduckgo.md │ │ ├── dynapictures.md │ │ ├── eatstreet.md │ │ ├── ebsco-information-services.md │ │ ├── ebury.md │ │ ├── eco-mind.md │ │ ├── ecosmic.md │ │ ├── edgar.md │ │ ├── edify.md │ │ ├── elastic.md │ │ ├── elsewhen.md │ │ ├── embraer.md │ │ ├── employment-hero.md │ │ ├── emsisoft.md │ │ ├── encora.md │ │ ├── engineyard.md │ │ ├── enjoei.md │ │ ├── enok.md │ │ ├── entrision.md │ │ ├── envato.md │ │ ├── envoy.md │ │ ├── epam.md │ │ ├── epic-games.md │ │ ├── epilocal.md │ │ ├── episource.md │ │ ├── epsy-health.md │ │ ├── equal-experts-portugal.md │ │ ├── ergeon.md │ │ ├── estately.md │ │ ├── etch.md │ │ ├── etsy.md │ │ ├── evelo.md │ │ ├── evil-martians.md │ │ ├── evrone.md │ │ ├── exoscale.md │ │ ├── exportdata.md │ │ ├── extreme-networks.md │ │ ├── eyeo.md │ │ ├── factorialhr.md │ │ ├── fairwinds.md │ │ ├── faithlife.md │ │ ├── fastly.md │ │ ├── fdte.md │ │ ├── fetlife.md │ │ ├── ffw-agency.md │ │ ├── figma.md │ │ ├── filament-group.md │ │ ├── findem.md │ │ ├── findify.md │ │ ├── fingerprint.md │ │ ├── fire-engine-red.md │ │ ├── firehire.md │ │ ├── fis-global.md │ │ ├── fiverr.md │ │ ├── fivexl.md │ │ ├── fleetio.md │ │ ├── flexera.md │ │ ├── flightaware.md │ │ ├── flip.md │ │ ├── flowing.md │ │ ├── flowpath.md │ │ ├── fly-io.md │ │ ├── fmx.md │ │ ├── focusnetworks.md │ │ ├── foh-and-boh.md │ │ ├── folotop.md │ │ ├── formidable.md │ │ ├── formstack.md │ │ ├── four-kitchens.md │ │ ├── fraudio.md │ │ ├── freeagent.md │ │ ├── freeletics.md │ │ ├── fuel-made.md │ │ ├── full-fabric.md │ │ ├── functionize.md │ │ ├── fyle.md │ │ ├── gaggle.md │ │ ├── geckoboard.md │ │ ├── general-assembly.md │ │ ├── geo-jobe.md │ │ ├── gerencianet.md │ │ ├── gft.md │ │ ├── ghost-inspector.md │ │ ├── giant-swarm.md │ │ ├── giant.md │ │ ├── gigsalad.md │ │ ├── gitbook.md │ │ ├── github.md │ │ ├── gitlab.md │ │ ├── gitprime.md │ │ ├── glenn-website-design.md │ │ ├── glitch.md │ │ ├── gluware.md │ │ ├── gmbapi.md │ │ ├── godaddy.md │ │ ├── gohiring.md │ │ ├── gojob.md │ │ ├── goldfire-agency.md │ │ ├── gorman-health-group.md │ │ ├── got-soccer.md │ │ ├── grafana.md │ │ ├── granicus.md │ │ ├── graylog.md │ │ ├── greenstitch-io.md │ │ ├── gremlin.md │ │ ├── gridium.md │ │ ├── groove.md │ │ ├── grou-ps.md │ │ ├── grubhub.md │ │ ├── gruntwork.md │ │ ├── guidesmiths.md │ │ ├── hack-reactor-remote.md │ │ ├── hanzo.md │ │ ├── happy-cog.md │ │ ├── harris-consult.md │ │ ├── harvest.md │ │ ├── hashex.md │ │ ├── hashicorp.md │ │ ├── he-labs.md │ │ ├── headforwards.md │ │ ├── headway.md │ │ ├── healthfinch.md │ │ ├── heap.md │ │ ├── heetch.md │ │ ├── help-scout.md │ │ ├── heroku.md │ │ ├── hireology.md │ │ ├── homeflic-wegrow.md │ │ ├── homevalet.md │ │ ├── honeybadger.md │ │ ├── honeycomb.md │ │ ├── hopper.md │ │ ├── hotjar.md │ │ ├── hubspot.md │ │ ├── hubstaff.md │ │ ├── hudl.md │ │ ├── hugo.md │ │ ├── human-made.md │ │ ├── husl-digital.md │ │ ├── hypergiant.md │ │ ├── hyperion.md │ │ ├── hypothesis.md │ │ ├── i-stem.md │ │ ├── ibm.md │ │ ├── iclinic.md │ │ ├── idonethis.md │ │ ├── ifit.md │ │ ├── igalia.md │ │ ├── imagine-learning.md │ │ ├── impala.md │ │ ├── impira.md │ │ ├── implisense.md │ │ ├── incsub.md │ │ ├── indrive.md │ │ ├── infinite-red.md │ │ ├── influxdata.md │ │ ├── inquicker.md │ │ ├── inshorts.md │ │ ├── instamobile.md │ │ ├── instructure.md │ │ ├── intellum.md │ │ ├── intent.md │ │ ├── inter-link.md │ │ ├── interactive-intelligence.md │ │ ├── intercom.md │ │ ├── interpersonal-frequency-i-f.md │ │ ├── intevity.md │ │ ├── intuit.md │ │ ├── intuition-machines-inc.md │ │ ├── invesco.md │ │ ├── iohk.md │ │ ├── iopipe.md │ │ ├── ios-app-templates.md │ │ ├── ipinfo.md │ │ ├── ips-group-inc.md │ │ ├── iqvia.md │ │ ├── ironin.md │ │ ├── iterative.md │ │ ├── iwantmyname.md │ │ ├── jackson-river.md │ │ ├── jaya-tech.md │ │ ├── jbs-custom-software-solutions.md │ │ ├── jitbit.md │ │ ├── jitera.md │ │ ├── jobsity.md │ │ ├── jolly-good-code.md │ │ ├── joor.md │ │ ├── journy-io.md │ │ ├── joyent.md │ │ ├── jupiterone.md │ │ ├── kake.md │ │ ├── karat.md │ │ ├── kaufland-e-commerce.md │ │ ├── kea.md │ │ ├── keen-io.md │ │ ├── kentik.md │ │ ├── kestra.md │ │ ├── khan-academy.md │ │ ├── kickback-rewards-systems.md │ │ ├── kindred.md │ │ ├── kinsta.md │ │ ├── kiprosh.md │ │ ├── kissmetrics.md │ │ ├── klanik.md │ │ ├── klaviyo.md │ │ ├── knack.md │ │ ├── kodify.md │ │ ├── koding.md │ │ ├── komoot.md │ │ ├── kona.md │ │ ├── konkurenta.md │ │ ├── kraken.md │ │ ├── kuali.md │ │ ├── labelbox.md │ │ ├── lambda-school.md │ │ ├── lambert-labs.md │ │ ├── laterpay.md │ │ ├── launch-potato.md │ │ ├── leadership-success.md │ │ ├── leadfeeder.md │ │ ├── leadiq.md │ │ ├── lets-encrypt.md │ │ ├── lifen.md │ │ ├── lifetime-value-company.md │ │ ├── lightspeed.md │ │ ├── linaro.md │ │ ├── lincoln-loop.md │ │ ├── line-plus-corporation.md │ │ ├── link11.md │ │ ├── linux-foundation.md │ │ ├── lionsher.md │ │ ├── litmus.md │ │ ├── liveperson.md │ │ ├── loadsys.md │ │ ├── localistico.md │ │ ├── locus-robotics.md │ │ ├── logdna.md │ │ ├── logdog.md │ │ ├── logrocket.md │ │ ├── lullabot.md │ │ ├── luxoft.md │ │ ├── luxor.md │ │ ├── lyseon-tech.md │ │ ├── lytx.md │ │ ├── madewithlove.md │ │ ├── madisoft.md │ │ ├── mailerlite.md │ │ ├── maintainnow.md │ │ ├── manifold.md │ │ ├── mapbox.md │ │ ├── marco-polo.md │ │ ├── mariadb.md │ │ ├── marketade.md │ │ ├── marsbased.md │ │ ├── massive-pixel-creation.md │ │ ├── mattermost.md │ │ ├── maxicus.md │ │ ├── mayven-studios.md │ │ ├── mayvue.md │ │ ├── meant4.md │ │ ├── mediacurrent.md │ │ ├── mediavine.md │ │ ├── medium.md │ │ ├── memberful.md │ │ ├── memory.md │ │ ├── mercari.md │ │ ├── merico.md │ │ ├── meridianlink.md │ │ ├── messagebird.md │ │ ├── metalab.md │ │ ├── metamask.md │ │ ├── meteorops.md │ │ ├── microsoft.md │ │ ├── mindful.md │ │ ├── mixcloud.md │ │ ├── mixmax.md │ │ ├── mixrank.md │ │ ├── mobile-jazz.md │ │ ├── modern-health.md │ │ ├── modern-tribe.md │ │ ├── modsquad.md │ │ ├── molteo.md │ │ ├── mongodb.md │ │ ├── monthly.md │ │ ├── mozilla.md │ │ ├── mtc.md │ │ ├── muck-rack.md │ │ ├── mux.md │ │ ├── mvpmule.md │ │ ├── mycelium.md │ │ ├── mysql.md │ │ ├── nagarro.md │ │ ├── namecheap.md │ │ ├── nationwide.md │ │ ├── nearform.md │ │ ├── nerdwallet.md │ │ ├── netapp.md │ │ ├── netguru.md │ │ ├── netlify.md │ │ ├── netris.md │ │ ├── netsparker.md │ │ ├── nettl-edinburgh.md │ │ ├── new-context.md │ │ ├── next.md │ │ ├── no-code-no-problem.md │ │ ├── nodesource.md │ │ ├── noredink.md │ │ ├── novoda.md │ │ ├── npm.md │ │ ├── nuage.md │ │ ├── nuharbor-security.md │ │ ├── nuna.md │ │ ├── nvidia.md │ │ ├── ocient.md │ │ ├── octopus-deploy.md │ │ ├── oddball.md │ │ ├── okta.md │ │ ├── olark.md │ │ ├── olist.md │ │ ├── ollie-order.md │ │ ├── ollie.md │ │ ├── olo.md │ │ ├── ombu-labs.md │ │ ├── omniti.md │ │ ├── on-the-go-systems.md │ │ ├── onna.md │ │ ├── opencity-labs.md │ │ ├── opencraft.md │ │ ├── openzeppelin.md │ │ ├── optoro.md │ │ ├── oracle.md │ │ ├── ordermentum.md │ │ ├── oreilly-media.md │ │ ├── oreilly-online-learning.md │ │ ├── our-hometown-inc.md │ │ ├── outsourcingdev.md │ │ ├── over.md │ │ ├── packlink.md │ │ ├── pagepro.md │ │ ├── pagerduty.md │ │ ├── paktor.md │ │ ├── palantir-net.md │ │ ├── panther-labs.md │ │ ├── parabol.md │ │ ├── parexel.md │ │ ├── park-assist.md │ │ ├── parsely.md │ │ ├── particular-software.md │ │ ├── pathable.md │ │ ├── payfully.md │ │ ├── paylocity.md │ │ ├── paypay.md │ │ ├── payscale.md │ │ ├── paytm-labs.md │ │ ├── payu.md │ │ ├── peachworks.md │ │ ├── peakforce.md │ │ ├── percona.md │ │ ├── pex.md │ │ ├── picpay.md │ │ ├── pindrop.md │ │ ├── plai.md │ │ ├── platform-builders.md │ │ ├── platform-sh.md │ │ ├── pleo.md │ │ ├── plex.md │ │ ├── pnc-financial-services.md │ │ ├── polygon.md │ │ ├── positiwise.md │ │ ├── powerschool.md │ │ ├── pragma.md │ │ ├── precision-nutrition.md │ │ ├── predict-mobile.md │ │ ├── prelude.md │ │ ├── previousnext.md │ │ ├── prezi.md │ │ ├── prezly.md │ │ ├── primer.md │ │ ├── primotly.md │ │ ├── prisma.md │ │ ├── privacycloud.md │ │ ├── procenge.md │ │ ├── procurify.md │ │ ├── progress-engine.md │ │ ├── prominent-edge.md │ │ ├── puppet.md │ │ ├── pwc.md │ │ ├── qatium.md │ │ ├── quaderno.md │ │ ├── quantify.md │ │ ├── questdb.md │ │ ├── quicktrials.md │ │ ├── quora.md │ │ ├── rackspace.md │ │ ├── raft.md │ │ ├── railscarma.md │ │ ├── rainforest-qa.md │ │ ├── rakuten-travel-xchange.md │ │ ├── ramp.md │ │ ├── reaction-commerce.md │ │ ├── reactiveops.md │ │ ├── realtimecrm.md │ │ ├── rebelmouse.md │ │ ├── reboot-studio.md │ │ ├── recharge.md │ │ ├── rechat.md │ │ ├── recurly.md │ │ ├── red-hat.md │ │ ├── reddit.md │ │ ├── redlio-designs.md │ │ ├── redmonk.md │ │ ├── redox.md │ │ ├── reducer.md │ │ ├── refundid.md │ │ ├── reinteractive.md │ │ ├── remote-garage.md │ │ ├── remotebase.md │ │ ├── remotemore.md │ │ ├── renaissance.md │ │ ├── rendr-software-group.md │ │ ├── renofi.md │ │ ├── replit.md │ │ ├── research-square.md │ │ ├── revolgy.md │ │ ├── revolut.md │ │ ├── roadpass-digital.md │ │ ├── roadtrippers.md │ │ ├── rocket-chat.md │ │ ├── roundproxies.md │ │ ├── rtcamp-solutions.md │ │ ├── sadapay.md │ │ ├── safeguard-global.md │ │ ├── salesforce.md │ │ ├── sandhills-development.md │ │ ├── sardine-ai.md │ │ ├── scalac.md │ │ ├── scaloy.md │ │ ├── scandit.md │ │ ├── schnell-solutions-limited.md │ │ ├── scopic-software.md │ │ ├── scrapingbee.md │ │ ├── scrapinghub.md │ │ ├── scylladb.md │ │ ├── seaplane.md │ │ ├── seatgeek.md │ │ ├── securityscorecard.md │ │ ├── seeq.md │ │ ├── semaphore.md │ │ ├── sendwave.md │ │ ├── serpapi.md │ │ ├── server-density.md │ │ ├── servmask.md │ │ ├── session.md │ │ ├── shareup.md │ │ ├── shattered-silicon.md │ │ ├── shippabo.md │ │ ├── shogun.md │ │ ├── shopify.md │ │ ├── sigma-defense.md │ │ ├── signeasy.md │ │ ├── silverfin.md │ │ ├── simplabs.md │ │ ├── simpletexting.md │ │ ├── six-to-start.md │ │ ├── sketch.md │ │ ├── skillcrush.md │ │ ├── skillshare.md │ │ ├── skyrocket-ventures.md │ │ ├── slack.md │ │ ├── smartcash.md │ │ ├── smile.md │ │ ├── smmile-digital.md │ │ ├── smugmug.md │ │ ├── socket-supply-co.md │ │ ├── softwaremill.md │ │ ├── sommo.md │ │ ├── sonatype.md │ │ ├── soostone.md │ │ ├── soshace.md │ │ ├── sourcegraph.md │ │ ├── spoqa.md │ │ ├── spotify.md │ │ ├── spreaker.md │ │ ├── spreedly.md │ │ ├── spruce.md │ │ ├── stack-exchange.md │ │ ├── stairlin.md │ │ ├── status.md │ │ ├── stencil.md │ │ ├── sticker-mule.md │ │ ├── stitch-fix.md │ │ ├── stoneco.md │ │ ├── stormx.md │ │ ├── strapi.md │ │ ├── streamnative.md │ │ ├── stripe.md │ │ ├── studysoup.md │ │ ├── superplayer-and-co.md │ │ ├── surevine.md │ │ ├── suse.md │ │ ├── sutherland-cloudsource.md │ │ ├── svix.md │ │ ├── sweetrush.md │ │ ├── swif-ai.md │ │ ├── swiggy.md │ │ ├── swimlane.md │ │ ├── syde.md │ │ ├── sysdig.md │ │ ├── tag1-consulting.md │ │ ├── taledo.md │ │ ├── taplytics.md │ │ ├── taskade.md │ │ ├── tatvasoft.md │ │ ├── taxjar.md │ │ ├── teamflow.md │ │ ├── teamsnap.md │ │ ├── teamultra.md │ │ ├── ted.md │ │ ├── teleport.md │ │ ├── telerik.md │ │ ├── telestax.md │ │ ├── tenable.md │ │ ├── teravision-technologies.md │ │ ├── test-double.md │ │ ├── testgorilla.md │ │ ├── the-crafters-lab.md │ │ ├── the-ghost-foundation.md │ │ ├── the-home-depot.md │ │ ├── the-planet-group.md │ │ ├── the-publisher-desk.md │ │ ├── the-scale-factory.md │ │ ├── the-wirecutter.md │ │ ├── theoremone.md │ │ ├── thinkful.md │ │ ├── third-iron.md │ │ ├── thorn.md │ │ ├── three-movers.md │ │ ├── thrive-market.md │ │ ├── tide.md │ │ ├── timespot.md │ │ ├── tipe.md │ │ ├── toast.md │ │ ├── toggl.md │ │ ├── toptal.md │ │ ├── tower.md │ │ ├── tractionboard.md │ │ ├── transfeera.md │ │ ├── transition-technologies-advanced-solutions.md │ │ ├── transloadit.md │ │ ├── travis-ci.md │ │ ├── travis.md │ │ ├── treehouse.md │ │ ├── treez.md │ │ ├── trello.md │ │ ├── tribe.md │ │ ├── truelogic.md │ │ ├── trussworks.md │ │ ├── tuft-and-needle.md │ │ ├── turing.md │ │ ├── turtlemint.md │ │ ├── twilio.md │ │ ├── two.md │ │ ├── udacity.md │ │ ├── uhuru.md │ │ ├── uncapped.md │ │ ├── upcell.md │ │ ├── upwork-pro.md │ │ ├── upworthy.md │ │ ├── usaa.md │ │ ├── ushahidi.md │ │ ├── uship.md │ │ ├── v0-report.md │ │ ├── valimail.md │ │ ├── varnish-software.md │ │ ├── vast-limits.md │ │ ├── veeva.md │ │ ├── vercel.md │ │ ├── verve-systems.md │ │ ├── veryfi.md │ │ ├── vgs.md │ │ ├── viperdev.md │ │ ├── virta-health.md │ │ ├── vivo.md │ │ ├── vmware.md │ │ ├── voiio.md │ │ ├── vox-media.md │ │ ├── voxy.md │ │ ├── vshn.md │ │ ├── wallethub.md │ │ ├── webdevstudios.md │ │ ├── webfx.md │ │ ├── webikon.md │ │ ├── webrunners.md │ │ ├── webscrapinghq.md │ │ ├── wells-fargo.md │ │ ├── wemake-services.md │ │ ├── wemakemvp.md │ │ ├── whitecap-seo.md │ │ ├── whitespectre.md │ │ ├── wikihow.md │ │ ├── wikimedia-foundation.md │ │ ├── wildbit.md │ │ ├── willings-inc.md │ │ ├── wingify.md │ │ ├── wipro.md │ │ ├── wirefuture.md │ │ ├── wizeline.md │ │ ├── wolfram.md │ │ ├── wolverine-trading.md │ │ ├── wombat-security.md │ │ ├── wp-media.md │ │ ├── wyeworks.md │ │ ├── x-team.md │ │ ├── xapo.md │ │ ├── xp-inc.md │ │ ├── xwp.md │ │ ├── yahoo.md │ │ ├── yandex.md │ │ ├── yazio.md │ │ ├── yodo1.md │ │ ├── yonder.md │ │ ├── you-are-launched.md │ │ ├── you-need-a-budget.md │ │ ├── youcanbook-me.md │ │ ├── zamp.md │ │ ├── zaneffi.md │ │ ├── zapier.md │ │ ├── zeit-io.md │ │ ├── zemoso.md │ │ ├── zenrows.md │ │ ├── zignaly.md │ │ ├── zip-co.md │ │ ├── zolar.md │ │ ├── zootools.md │ │ └── zup.md │ └── pages/ │ ├── about.md │ ├── blog.njk │ ├── changelog.njk │ ├── companies.njk │ ├── contact.md │ ├── contributing.md │ ├── index.njk │ ├── privacy.md │ ├── search.njk │ ├── tag.njk │ └── tags.njk └── tailwind.config.js
SYMBOL INDEX (32 symbols across 10 files)
FILE: .github/scripts/validate-companies.js
constant VALID_REGIONS (line 15) | const VALID_REGIONS = [
constant VALID_REMOTE_POLICIES (line 24) | const VALID_REMOTE_POLICIES = [
constant VALID_COMPANY_SIZES (line 31) | const VALID_COMPANY_SIZES = ["tiny", "small", "medium", "large", "enterp...
constant VALID_TECHNOLOGIES (line 33) | const VALID_TECHNOLOGIES = [
constant REQUIRED_FIELDS (line 54) | const REQUIRED_FIELDS = [
constant REQUIRED_SECTIONS (line 63) | const REQUIRED_SECTIONS = ["Company blurb", "Remote status", "How to app...
function parseFrontmatter (line 69) | function parseFrontmatter(content) {
function validateCompanyFile (line 117) | function validateCompanyFile(filePath) {
function isValidUrl (line 223) | function isValidUrl(str) {
function escapeRegExp (line 232) | function escapeRegExp(str) {
FILE: src/_config/filters/split.js
function split (line 7) | function split(str, delimiter = '/') {
FILE: src/_config/plugins/markdown.js
method matcher (line 35) | matcher(href) {
FILE: src/_config/setup/generate-favicons.js
function createFavicons (line 6) | async function createFavicons() {
FILE: src/_data/companyHelpers.js
function getRegionLabel (line 15) | function getRegionLabel(region) {
function getRemotePolicyLabel (line 19) | function getRemotePolicyLabel(policy) {
function getCompanySizeLabel (line 23) | function getCompanySizeLabel(size) {
function getTechLabel (line 27) | function getTechLabel(tech) {
FILE: src/_data/helpers.js
function getLinkActiveState (line 9) | function getLinkActiveState(itemUrl, pageUrl) {
function filterCollectionByKeys (line 35) | function filterCollectionByKeys(collection, keys) {
function random (line 44) | function random() {
FILE: src/assets/scripts/bundle/dialog.js
function handleSwipe (line 62) | function handleSwipe() {
FILE: src/assets/scripts/bundle/nav-sub.js
function closeSubmenu (line 41) | function closeSubmenu(buttonSub) {
FILE: src/assets/scripts/bundle/theme-toggle.js
function onClick (line 37) | function onClick(themeValue) {
function getColorPreference (line 45) | function getColorPreference() {
function setPreference (line 53) | function setPreference() {
function reflectPreference (line 59) | function reflectPreference() {
function updateMetaThemeColor (line 63) | function updateMetaThemeColor() {
FILE: src/assets/scripts/components/custom-masonry.js
class CustomMasonry (line 1) | class CustomMasonry extends HTMLElement {
method constructor (line 2) | constructor() {
method connectedCallback (line 11) | connectedCallback() {
method disconnectedCallback (line 19) | disconnectedCallback() {
method layoutMasonry (line 24) | layoutMasonry() {
Condensed preview — 1039 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,610K chars).
[
{
"path": ".eleventyignore",
"chars": 506,
"preview": "# Eleventy ignore file\n# Files and directories that Eleventy should not process\n\n# Node modules\nnode_modules/\n\n# README "
},
{
"path": ".env-sample",
"chars": 25,
"preview": "URL=http://localhost:8080"
},
{
"path": ".github/CODE_OF_CONDUCT.md",
"chars": 5603,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 6839,
"preview": "# Contributing to Remote In Tech\n\nThank you for your interest in contributing! This repository maintains a list of compa"
},
{
"path": ".github/FUNDING.yml",
"chars": 66,
"preview": "# These are supported funding model platforms\n\ngithub: dougaitken\n"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 840,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[Bug]\"\nlabels: bug\nassignees: ''\n\n---\n\n**Describe"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 398,
"preview": "blank_issues_enabled: false\ncontact_links:\n - name: Feature Requests & Ideas\n url: https://github.com/remoteintech/r"
},
{
"path": ".github/ISSUE_TEMPLATE/new_company.md",
"chars": 1548,
"preview": "---\nname: New Company Request\nabout: Request a remote-friendly company to be added to the directory\ntitle: \"[Company] \"\n"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.MD",
"chars": 1703,
"preview": "## Description\n\n<!-- Briefly describe your changes -->\n\n## Type of Change\n\n- [ ] New company addition\n- [ ] Company info"
},
{
"path": ".github/dependabot.yml",
"chars": 177,
"preview": "version: 2\nupdates:\n - package-ecosystem: npm\n directory: /\n schedule:\n interval: weekly\n allow:\n - "
},
{
"path": ".github/scripts/validate-companies.js",
"chars": 7232,
"preview": "#!/usr/bin/env node\n\n/**\n * Validates company profile files for correct frontmatter and content structure.\n *\n * Usage: "
},
{
"path": ".github/workflows/ci.yml",
"chars": 684,
"preview": "name: CI\n\non:\n push:\n branches: [main]\n pull_request:\n branches: [main]\n workflow_dispatch:\n\njobs:\n build:\n "
},
{
"path": ".github/workflows/codeql-analysis.yml",
"chars": 1281,
"preview": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# Y"
},
{
"path": ".github/workflows/validate-companies.yml",
"chars": 7282,
"preview": "name: Validate Company Profiles\n\non:\n pull_request_target:\n branches: [main]\n paths:\n - 'src/companies/**'\n "
},
{
"path": ".gitignore",
"chars": 742,
"preview": "# Node modules\nnode_modules\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# generated files\ndist\nsrc/_includes/css\nsrc"
},
{
"path": ".nojekyll",
"chars": 0,
"preview": ""
},
{
"path": ".nvmrc",
"chars": 3,
"preview": "22\n"
},
{
"path": ".prettierignore",
"chars": 89,
"preview": "src/**/*.md\nsrc/_includes/components/**/custom-*.njk\nsrc/common/*\nsrc/_includes/scripts/*"
},
{
"path": ".prettierrc",
"chars": 343,
"preview": "{\n \"printWidth\": 110,\n \"tabWidth\": 2,\n \"singleQuote\": true,\n \"bracketSpacing\": false,\n \"quoteProps\": \"consistent\",\n"
},
{
"path": "CLAUDE.md",
"chars": 11245,
"preview": "# CLAUDE.md\n\n## Shorthand\n\n- **\"Punch it\"** = commit, push, and open a PR to upstream, all in one go.\n\n## Project Overvi"
},
{
"path": "LICENSE",
"chars": 2332,
"preview": "## Licenses\n\nThis project is licensed under the ISC License. Additionally, it includes components that are licensed unde"
},
{
"path": "README.md",
"chars": 1564,
"preview": "# Remote In Tech\n\n**[remoteintech.company](https://remoteintech.company)** — A community-maintained directory of remote-"
},
{
"path": "eleventy.config.js",
"chars": 4860,
"preview": "/**\n * Most adjustments must be made in `./src/_config/*`\n *\n * Hint VS Code for eleventyConfig autocompletion.\n * © Hen"
},
{
"path": "package.json",
"chars": 2442,
"preview": "{\n \"name\": \"remote-in-tech\",\n \"version\": \"4.4.1\",\n \"description\": \"A list of semi to fully remote-friendly companies "
},
{
"path": "src/_config/collections.js",
"chars": 5615,
"preview": "import {\n featuredCompanySlugs,\n regionLabels,\n remotePolicyLabels,\n techLabels\n} from '../_data/companyHelpers.js';"
},
{
"path": "src/_config/events/build-css.js",
"chars": 1496,
"preview": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport postcss from 'postcss';\nimport postcssImport fro"
},
{
"path": "src/_config/events/build-js.js",
"chars": 1174,
"preview": "import fs from 'node:fs/promises';\nimport path from 'node:path';\nimport fg from 'fast-glob';\nimport esbuild from 'esbuil"
},
{
"path": "src/_config/events/svg-to-jpeg.js",
"chars": 1077,
"preview": "import {promises as fsPromises, existsSync} from 'node:fs';\nimport path from 'node:path';\nimport Image from '@11ty/eleve"
},
{
"path": "src/_config/events.js",
"chars": 213,
"preview": "import {svgToJpeg} from './events/svg-to-jpeg.js';\nimport {buildAllCss} from './events/build-css.js';\nimport {buildAllJs"
},
{
"path": "src/_config/filters/dates.js",
"chars": 323,
"preview": "import dayjs from 'dayjs';\n\n/** Converts the given date string to ISO8610 format. */\nexport const toISOString = dateStri"
},
{
"path": "src/_config/filters/fileExists.js",
"chars": 202,
"preview": "import fs from 'fs';\nimport path from 'path';\n\nexport const fileExists = (filePath) => {\n try {\n return fs.existsSyn"
},
{
"path": "src/_config/filters/markdown-format.js",
"chars": 252,
"preview": "// by Chris Burnell: https://chrisburnell.com/article/some-eleventy-filters/#markdown-format\n\nimport markdownParser from"
},
{
"path": "src/_config/filters/slugify.js",
"chars": 222,
"preview": "import slugify from 'slugify';\n\n/** Converts string to a slug form. */\nexport const slugifyString = str => {\n return sl"
},
{
"path": "src/_config/filters/sort-alphabetic.js",
"chars": 195,
"preview": "export const sortAlphabetically = array => {\n return array.sort((a, b) => {\n if (a.data.title < b.data.title) return"
},
{
"path": "src/_config/filters/sort-random.js",
"chars": 258,
"preview": "export const shuffleArray = array => {\n const shuffled = [...array];\n for (let i = shuffled.length - 1; i > 0; i--) {\n"
},
{
"path": "src/_config/filters/split.js",
"chars": 346,
"preview": "/**\n * Split a string by a delimiter, filtering out empty strings\n * @param {string} str - String to split\n * @param {st"
},
{
"path": "src/_config/filters/splitlines.js",
"chars": 414,
"preview": "export const splitlines = (input, maxCharLength) => {\n const parts = input.split(' ');\n const lines = parts.reduce(fun"
},
{
"path": "src/_config/filters/striptags.js",
"chars": 242,
"preview": "// Using single-character replacement as recommended by CodeQL to avoid\n// incomplete multi-character sanitization vulne"
},
{
"path": "src/_config/filters.js",
"chars": 590,
"preview": "import {toISOString, formatDate} from './filters/dates.js';\nimport {markdownFormat} from './filters/markdown-format.js';"
},
{
"path": "src/_config/plugins/drafts.js",
"chars": 990,
"preview": "export const drafts = eleventyConfig => {\n eleventyConfig.addGlobalData('eleventyComputed.permalink', function () {\n "
},
{
"path": "src/_config/plugins/html-config.js",
"chars": 540,
"preview": "import htmlmin from 'html-minifier-terser';\n\nconst isProduction = process.env.ELEVENTY_ENV === 'production';\n\nexport con"
},
{
"path": "src/_config/plugins/markdown.js",
"chars": 2060,
"preview": "import markdownIt from 'markdown-it';\nimport markdownItAttrs from 'markdown-it-attrs';\nimport markdownItPrism from 'mark"
},
{
"path": "src/_config/plugins.js",
"chars": 621,
"preview": "// Eleventy\nimport {EleventyRenderPlugin} from '@11ty/eleventy';\nimport rss from '@11ty/eleventy-plugin-rss';\nimport syn"
},
{
"path": "src/_config/setup/create-colors.js",
"chars": 3263,
"preview": "import fs from 'node:fs';\nimport Color from 'colorjs.io';\n\nconst colorsBase = JSON.parse(fs.readFileSync('./src/_data/de"
},
{
"path": "src/_config/setup/generate-favicons.js",
"chars": 1156,
"preview": "import fs from 'node:fs';\nimport sharp from 'sharp';\nimport {sharpsToIco} from 'sharp-ico';\nimport {pathToSvgLogo} from "
},
{
"path": "src/_config/shortcodes/image.js",
"chars": 2122,
"preview": "import Image from '@11ty/eleventy-img';\nimport path from 'node:path';\nimport fs from 'fs';\n\nconst stringifyAttributes = "
},
{
"path": "src/_config/shortcodes/svg.js",
"chars": 917,
"preview": "/**\n * Generates an optimized SVG shortcode with optional attributes.\n *\n * @param {string} svgName - The name of the SV"
},
{
"path": "src/_config/shortcodes.js",
"chars": 152,
"preview": "import {imageShortcode} from './shortcodes/image.js';\nimport {svgShortcode} from './shortcodes/svg.js';\n\nexport default "
},
{
"path": "src/_config/utils/clamp-generator.js",
"chars": 1229,
"preview": "/**\n * Credits:\n * - © Andy Bell - https://buildexcellentwebsit.es/\n */\n\n/**\n * Takes an array of tokens and sends back "
},
{
"path": "src/_config/utils/tokens-to-tailwind.js",
"chars": 499,
"preview": "/**\n * Credits:\n * - © Andy Bell - https://buildexcellentwebsit.es/\n */\n\n/**\n * Converts human readable tokens into tail"
},
{
"path": "src/_data/changelog.json",
"chars": 4894,
"preview": "[\n {\n \"version\": \"4.4.1\",\n \"date\": \"2026-02-22\",\n \"summary\": \"Date field documentation\",\n \"changes\": [\n "
},
{
"path": "src/_data/companyHelpers.js",
"chars": 1091,
"preview": "/**\n * Helper functions and constants for company data.\n * Label maps are imported from labels.js (the single source of "
},
{
"path": "src/_data/designTokens/borderRadius.json",
"chars": 207,
"preview": "{\n \"title\": \"Border Radius\",\n \"description\": \"\",\n \"meta\": {},\n \"items\": [\n {\n \"name\": \"small\",\n \"value\""
},
{
"path": "src/_data/designTokens/colors.json",
"chars": 1511,
"preview": "{\n \"title\": \"Colors\",\n \"description\": \"Hex color codes that can be shared, cross-platform. They can be converted at po"
},
{
"path": "src/_data/designTokens/colorsBase.json",
"chars": 793,
"preview": "{\n \"title\": \"Colors\",\n \"description\": \"Hex color codes that can be shared, cross-platform. They can be converted at po"
},
{
"path": "src/_data/designTokens/fonts.json",
"chars": 1178,
"preview": "{\n \"title\": \"Fonts\",\n \"description\": \"Each array of fonts creates a priority-based order. The first font in the array "
},
{
"path": "src/_data/designTokens/spacing.json",
"chars": 1547,
"preview": "{\n \"title\": \"Spacing\",\n \"description\": \"Consistent spacing sizes, based on a ratio, with min and max sizes. This allow"
},
{
"path": "src/_data/designTokens/textLeading.json",
"chars": 265,
"preview": "{\n \"title\": \"Leading\",\n \"description\": \"Ratio-based leading/line-height values\",\n \"items\": [\n {\n \"name\": \"Fla"
},
{
"path": "src/_data/designTokens/textSizes.json",
"chars": 1063,
"preview": "{\n \"title\": \"Text Sizes\",\n \"description\": \"A minimum and maximum text size size allows you to pick the right size from"
},
{
"path": "src/_data/designTokens/textWeights.json",
"chars": 313,
"preview": "{\n \"title\": \"Text Weights\",\n \"description\": \"Helper classes and custom properties for common font weights\",\n \"meta\": "
},
{
"path": "src/_data/designTokens/viewports.json",
"chars": 200,
"preview": "{\n \"title\": \"Viewports\",\n \"description\": \"The min and maximum viewports used to generate fluid type and space scales.\""
},
{
"path": "src/_data/github.js",
"chars": 281,
"preview": "import EleventyFetch from '@11ty/eleventy-fetch';\n\nexport default async function () {\n let url = 'https://api.github.co"
},
{
"path": "src/_data/helpers.js",
"chars": 1390,
"preview": "/**\n * Returns back some attributes based on whether the\n * link is active or a parent of an active item.\n *\n * @param {"
},
{
"path": "src/_data/labels.js",
"chars": 1349,
"preview": "/**\n * Centralised label definitions for regions, remote policies, technologies, and company sizes.\n * This is the singl"
},
{
"path": "src/_data/meta.js",
"chars": 3045,
"preview": "export const url = process.env.URL || 'http://localhost:8080';\n// Extract domain from `url`\nexport const domain = new UR"
},
{
"path": "src/_data/navigation.js",
"chars": 526,
"preview": "export default {\n top: [\n {\n text: 'Companies',\n url: '/companies/'\n },\n {\n text: 'Browse',\n "
},
{
"path": "src/_data/personal.js",
"chars": 86,
"preview": "export const platforms = {\n github: 'https://github.com/remoteintech/remote-jobs'\n};\n"
},
{
"path": "src/_includes/head/css-inline.njk",
"chars": 509,
"preview": "\n\t{%- if eleventy.env.runMode === \"serve\" %}\n\t\t<!-- External CSS for local dev (refresh without page reload) -->\n\t\t<link"
},
{
"path": "src/_includes/head/js-defer.njk",
"chars": 75,
"preview": "<script src=\"{% getBundleFileUrl \"js\", \"defer\" %}\" type=\"module\"></script>\n"
},
{
"path": "src/_includes/head/js-inline.njk",
"chars": 187,
"preview": "\n<script>{% getBundle \"js\", \"inline\" %}</script>\n\n{%- js \"inline\" %} <!-- inlined for all pages -->\n{% include \"scripts/"
},
{
"path": "src/_includes/head/meta-info.njk",
"chars": 3885,
"preview": "{% set metaDescription %}\n {%- if discover.description -%}\n {{- discover.description -}}\n {%- elif description -%}\n"
},
{
"path": "src/_includes/head/preloads.njk",
"chars": 644,
"preview": "<link\n rel=\"preload\"\n href=\"/assets/fonts/redhat/red-hat-display-v7-latin-900.woff2\"\n as=\"font\"\n type=\"font/woff2\"\n "
},
{
"path": "src/_includes/head/schema.njk",
"chars": 155,
"preview": "{% include \"schemas/WebSite.njk\" %}\n{% include \"schemas/BreadcrumbList.njk\" %}\n\n{% if schema %}\n {%- include \"schemas/\""
},
{
"path": "src/_includes/partials/card-tag.njk",
"chars": 480,
"preview": "{% set headingLevel = headingLevel | default(\"h2\") %}\n{% set definedDate = definedDate | default(item.date) %}\n\n<custom-"
},
{
"path": "src/_includes/partials/company-card.njk",
"chars": 1077,
"preview": "{# Company Card Macro - Reusable card component for company listings #}\n\n{% set regionLabels = {\n 'worldwide': 'Worldwi"
},
{
"path": "src/_includes/partials/date.njk",
"chars": 105,
"preview": "<time datetime=\"{{ definedDate | toIsoString }}\"> {{ definedDate | formatDate('MMMM D, YYYY') }} </time>\n"
},
{
"path": "src/_includes/partials/details.njk",
"chars": 657,
"preview": "<div class=\"details flow\">\n <div class=\"control | cluster\" aria-label=\"{{ meta.details.aria }}\">\n <button id=\"expand"
},
{
"path": "src/_includes/partials/edit-on.njk",
"chars": 273,
"preview": "{% if meta.viewRepo.allow %}\n <hr />\n <p class=\"text-step-min-1\">\n {{ meta[page.lang].blog.githubEdit }}\n <a hre"
},
{
"path": "src/_includes/partials/footer.njk",
"chars": 1869,
"preview": "<footer class=\"site-footer mt-l-xl p-s-m\">\n <div class=\"wrapper\">\n <div class=\"cluster gutter-xs\">\n <nav class="
},
{
"path": "src/_includes/partials/header.njk",
"chars": 492,
"preview": "<header class=\"wrapper\">\n <a href=\"#main\" class=\"skip-link\">{{ meta.skipContent }}</a>\n\n <div class=\"repel ontop gutte"
},
{
"path": "src/_includes/partials/main-nav.njk",
"chars": 2573,
"preview": "<!-- toggle drawer and sub menu in _data/meta.js -->\n{% set drawerNav = meta.navigation.drawerNav %}\n{% set subMenu = me"
},
{
"path": "src/_includes/partials/nav-search.njk",
"chars": 5110,
"preview": "<div class=\"nav-search\" id=\"nav-search\">\n <button class=\"nav-search__trigger\" aria-expanded=\"false\" aria-controls=\"nav-"
},
{
"path": "src/_includes/partials/pagination.njk",
"chars": 1372,
"preview": "<section class=\"region\">\n <nav role=\"navigation\" aria-labelledby=\"pagination_label\">\n <span id=\"pagination_label\" hi"
},
{
"path": "src/_includes/partials/theme-switch.njk",
"chars": 666,
"preview": "<is-land on:idle\n ><div\n role=\"region\"\n class=\"theme-switch | cluster\"\n style=\"--cluster-horizontal-alignment:"
},
{
"path": "src/_includes/schemas/BlogPosting.njk",
"chars": 847,
"preview": "<script type=\"application/ld+json\">\n {\n \"@context\": \"https://schema.org\",\n \"@type\": \"BlogPosting\",\n \"mainEntit"
},
{
"path": "src/_includes/schemas/BreadcrumbList.njk",
"chars": 1363,
"preview": "{# Generate breadcrumbs based on page URL - only for pages with paths #}\n{%- set cleanUrl = page.url | trim('/') -%}\n{%-"
},
{
"path": "src/_includes/schemas/Organization.njk",
"chars": 237,
"preview": "<script type=\"application/ld+json\">\n{\n \"@context\": \"https://schema.org\",\n \"@type\": \"Organization\",\n \"name\": \"{{ title"
},
{
"path": "src/_includes/schemas/WebSite.njk",
"chars": 649,
"preview": "<script type=\"application/ld+json\">\n {\n \"@context\": \"https://schema.org\",\n \"@graph\": [\n {\n \"@type\": \""
},
{
"path": "src/_includes/webc/custom-card.webc",
"chars": 273,
"preview": "<custom-card class=\"flow no-indicator\" webc:root>\n <slot name=\"image\"></slot>\n <slot name=\"headline\"></slot>\n <div cl"
},
{
"path": "src/_includes/webc/custom-masonry.webc",
"chars": 313,
"preview": "<is-land on:visible>\n <custom-masonry class=\"grid\" data-rows=\"masonry\" :data-layout=\"layout\" webc:root webc:keep>\n <"
},
{
"path": "src/_includes/webc/custom-peertube-link.webc",
"chars": 489,
"preview": "<style>\n custom-peertube-link {\n display: flex;\n align-items: flex-start;\n gap: var(--space-xs);\n font-size"
},
{
"path": "src/_includes/webc/custom-peertube.webc",
"chars": 1266,
"preview": "<script webc:setup>\n function getLinkUrl(instance, slug, start) {\n return `https://${instance}/w/${slug}${start ? `?"
},
{
"path": "src/_includes/webc/custom-svg.webc",
"chars": 1097,
"preview": "<script webc:type=\"render\" webc:is=\"template\">\n const Image = require('@11ty/eleventy-img');\n const {optimize} = requi"
},
{
"path": "src/_includes/webc/custom-youtube-link.webc",
"chars": 486,
"preview": "<style>\n custom-youtube-link {\n display: flex;\n align-items: flex-start;\n gap: var(--space-xs);\n font-size:"
},
{
"path": "src/_includes/webc/custom-youtube.webc",
"chars": 1970,
"preview": "<!-- component composition by: https://github.com/zachleat/zachleat.com -->\n\n<script webc:setup>\n function getPosterIma"
},
{
"path": "src/_layouts/base.njk",
"chars": 1850,
"preview": "{% set assetHash = helpers.random() %}\n\n<!doctype html>\n<html lang=\"{{ meta.lang }}\">\n <!-- The order of elements in th"
},
{
"path": "src/_layouts/company.njk",
"chars": 4361,
"preview": "---\nlayout: base\nschema: Organization\n---\n\n<article class=\"company-profile | region wrapper\" data-pagefind-body>\n <head"
},
{
"path": "src/_layouts/page.njk",
"chars": 185,
"preview": "---\nlayout: base\n---\n\n<div class=\"region wrapper flow prose\" style=\"--region-space-top: var(--space-xl-2xl)\">\n <h1 clas"
},
{
"path": "src/_layouts/post.njk",
"chars": 1525,
"preview": "---\nlayout: base\nschema: BlogPosting\n---\n\n<div class=\"region\" style=\"--region-space-top: var(--space-xl-2xl)\">\n <div cl"
},
{
"path": "src/_layouts/tags.njk",
"chars": 179,
"preview": "---\nlayout: base\n---\n\n<div class=\"region\">\n <div class=\"wrapper flow\">\n <h1 class=\"gradient-text-linear text-step-4\""
},
{
"path": "src/assets/css/global/base/fonts.css",
"chars": 893,
"preview": "@font-face {\n font-family: 'Atkinson Hyperlegible';\n font-style: normal;\n font-display: swap;\n font-weight: 400;\n s"
},
{
"path": "src/assets/css/global/base/global-styles.css",
"chars": 2394,
"preview": "/*\n\tGlobal styles\n\n\tLow-specificity, global styles that apply to the whole\n\tproject: https://cube.fyi/css.html\n*/\n\nhtml "
},
{
"path": "src/assets/css/global/base/reset.css",
"chars": 2036,
"preview": "/* https://piccalil.li/blog/a-modern-css-reset/ */\n/* https://keithjgrant.com/posts/2024/01/my-css-resets/ */\n/* https:/"
},
{
"path": "src/assets/css/global/base/variables.css",
"chars": 2297,
"preview": "/* Global variables. */\n\n/* Basic variable definitions for color schemes */\n:root {\n --gutter: var(--space-m-l);\n --t"
},
{
"path": "src/assets/css/global/blocks/button.css",
"chars": 3683,
"preview": "/* based on Andy Bell's article: https://piccalil.li/blog/how-i-build-a-button-component/ */\n\n.button {\n --button-bg: v"
},
{
"path": "src/assets/css/global/blocks/code.css",
"chars": 3656,
"preview": ":root {\n --color-code-orange: hsl(30, 70%, 60%);\n --color-code-blue: var(--color-secondary);\n --color-code-indigo: hs"
},
{
"path": "src/assets/css/global/blocks/company-card.css",
"chars": 1782,
"preview": "/* Company Card Component */\n\n.company-card {\n background-color: var(--color-bg);\n border: 2px solid var(--color-bg-ac"
},
{
"path": "src/assets/css/global/blocks/external-link.css",
"chars": 879,
"preview": "a[href^='http']:not([href*='localhost:8080']):not([href*='dougaitken.co.uk']):not(.button):not(\n .no-indicator\n ):no"
},
{
"path": "src/assets/css/global/blocks/main-nav.css",
"chars": 7618,
"preview": ".mainnav {\n --nav-list-timing-function: cubic-bezier(0.68, -0.55, 0.27, 1.55);\n position: var(--nav-position, absolute"
},
{
"path": "src/assets/css/global/blocks/prose.css",
"chars": 1754,
"preview": "/* Based on Andy Bell, https://github.com/Andy-set-studio/personal-site-eleventy */\n\n.prose {\n --flow-space: var(--spac"
},
{
"path": "src/assets/css/global/blocks/section.css",
"chars": 398,
"preview": ".section > .divider:first-child {\n transform: rotate(180deg) translateY(-1px);\n}\n\n.section__inner {\n background-color:"
},
{
"path": "src/assets/css/global/blocks/seperator.css",
"chars": 173,
"preview": ".divider {\n display: block;\n block-size: 3.5em;\n inline-size: 100%;\n fill: var(--spot-color, var(--color-bg));\n}\n\n.d"
},
{
"path": "src/assets/css/global/blocks/site-footer.css",
"chars": 397,
"preview": ".site-footer {\n --cluster-horizontal-alignment: center;\n}\n\n/* increase tab size */\n.site-footer a:not(.creator a) {\n p"
},
{
"path": "src/assets/css/global/blocks/site-logo.css",
"chars": 126,
"preview": ".logo {\n --gutter: var(--space-xs);\n padding: var(--space-s) 0;\n font-size: var(--size-step-0);\n text-decoration: no"
},
{
"path": "src/assets/css/global/blocks/skip-link.css",
"chars": 659,
"preview": ".skip-link {\n clip: rect(1px, 1px, 1px, 1px);\n display: block;\n block-size: 1px;\n overflow: hidden;\n position: abso"
},
{
"path": "src/assets/css/global/blocks/textgradient.css",
"chars": 327,
"preview": ".gradient-text {\n color: transparent;\n background-image: var(--gradient-conic);\n padding: 0.6rem 0;\n background-size"
},
{
"path": "src/assets/css/global/blocks/theme-switch.css",
"chars": 429,
"preview": ".theme-switch h2 {\n font-size: var(--size-step-min-1);\n font-family: var(--font-base);\n}\n\n.theme-switch .button[aria-p"
},
{
"path": "src/assets/css/global/compositions/cluster.css",
"chars": 792,
"preview": "/*\nCLUSTER\nMore info: https://every-layout.dev/layouts/cluster/\nA layout that lets you distribute items with consistent\n"
},
{
"path": "src/assets/css/global/compositions/flow.css",
"chars": 228,
"preview": "/*\nFLOW UTILITY\nLike the Every Layout stack: https://every-layout.dev/layouts/stack/\nInfo about this implementation: htt"
},
{
"path": "src/assets/css/global/compositions/grid.css",
"chars": 1102,
"preview": "/* AUTO GRID\nRelated Every Layout: https://every-layout.dev/layouts/grid/\nMore info on the flexible nature: https://picc"
},
{
"path": "src/assets/css/global/compositions/repel.css",
"chars": 576,
"preview": "/*\nREPEL\nA little layout that pushes items away from each other where\nthere is space in the viewport and stacks on small"
},
{
"path": "src/assets/css/global/compositions/sidebar.css",
"chars": 1353,
"preview": "/*\nSIDEBAR\nMore info: https://every-layout.dev/layouts/sidebar/\nA layout that allows you to have a flexible main content"
},
{
"path": "src/assets/css/global/compositions/wrapper.css",
"chars": 758,
"preview": "/* © Ryan Mulligan - https://ryanmulligan.dev/blog/layout-breakouts/ */\n\n.wrapper {\n --gap: clamp(1rem, 6vw, 3rem);\n -"
},
{
"path": "src/assets/css/global/global.css",
"chars": 554,
"preview": "@import 'tailwindcss/base' layer(tailwindBase);\n\n@import 'base/reset.css' layer(reset);\n@import 'base/fonts.css' layer(f"
},
{
"path": "src/assets/css/global/tests/debug.css",
"chars": 2469,
"preview": "/* https://heydonworks.com/article/testing-html-with-modern-css/ */\n\n/* WIP */\n\n:root {\n --highlight-outline: 0.25rem s"
},
{
"path": "src/assets/css/global/tests/is-land.css",
"chars": 340,
"preview": ":root {\n --island-ready: 0.25rem solid ForestGreen;\n --island-component: 0.25rem solid goldenrod;\n}\n\nis-land[ready] {\n"
},
{
"path": "src/assets/css/global/utilities/grayscale.css",
"chars": 42,
"preview": ".grayscale {\n filter: grayscale(100%);\n}\n"
},
{
"path": "src/assets/css/global/utilities/heading-line.css",
"chars": 291,
"preview": ".heading-line {\n display: flex;\n align-items: flex-start;\n text-align: left;\n}\n\n.heading-line::after {\n content: '';"
},
{
"path": "src/assets/css/global/utilities/ontop.css",
"chars": 104,
"preview": "/* set on parent div to get the right z-index context */\n.ontop {\n z-index: 1;\n position: relative;\n}\n"
},
{
"path": "src/assets/css/global/utilities/region.css",
"chars": 411,
"preview": "/**\n * REGION\n * Add consistent vertical padding to create regions of content\n * Can either be configured by setting --r"
},
{
"path": "src/assets/css/global/utilities/spin.css",
"chars": 218,
"preview": ".spin {\n animation: spin 30s linear infinite;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n @keyframes spin {\n "
},
{
"path": "src/assets/css/global/utilities/visually-hidden.css",
"chars": 259,
"preview": "/*\nVISUALLY HIDDEN UTILITY\nInfo: https://piccalil.li/quick-tip/visually-hidden/\n*/\n.visually-hidden {\n border: 0;\n cli"
},
{
"path": "src/assets/css/local/custom-card.css",
"chars": 2014,
"preview": "custom-card {\n --gutter: var(--space-xs-s);\n background-color: var(--card-bg, var(--color-bg-accent));\n border: 4px s"
},
{
"path": "src/assets/css/local/details.css",
"chars": 1823,
"preview": ".details .control {\n --gutter: var(--space-xs-s);\n --cluster-horizontal-alignment: flex-end;\n border-block-start: var"
},
{
"path": "src/assets/css/local/footnotes.css",
"chars": 495,
"preview": ".footnotes {\n font-size: var(--size-step-min-1);\n}\n\n.footnote-ref {\n font-size: var(--size-step-min-2);\n vertical-ali"
},
{
"path": "src/assets/css/local/forms.css",
"chars": 1273,
"preview": "/* © https://piccalil.li/complete-css */\n\nform > * + * {\n margin-top: var(--flow-space, 1rem);\n}\n\n:is(input:not([type='"
},
{
"path": "src/assets/css/local/gallery.css",
"chars": 906,
"preview": ".gallery dialog {\n border-radius: var(--border-radius-medium);\n inset: revert;\n}\n\n/* the image that serves as button *"
},
{
"path": "src/assets/css/local/nav-main-drawer-cls.css",
"chars": 107,
"preview": "@media (scripting: enabled) {\n @media screen(ltsm) {\n .mainnav > ul {\n display: none;\n }\n }\n}\n"
},
{
"path": "src/assets/css/local/pagination.css",
"chars": 935,
"preview": ".pagination {\n --gutter: var(--space-xs-s);\n}\n\n.pagination li {\n color: var(--pagination-text, var(--color-text));\n b"
},
{
"path": "src/assets/css/local/post.css",
"chars": 817,
"preview": ".post article h1 {\n font-size: var(--size-step-6);\n}\n\n.post article h2 {\n font-size: var(--size-step-3);\n}\n\n.post arti"
},
{
"path": "src/assets/css/local/styleguide.css",
"chars": 1474,
"preview": ".styleguide {\n --region-space-top: var(--space-xl-2xl);\n --spot-color: color-mix(in oklab, var(--color-bg) 97%, var(--"
},
{
"path": "src/assets/css/local/table.css",
"chars": 1263,
"preview": "table {\n border: 0;\n inline-size: 100%;\n}\n\ntable br {\n display: none;\n}\n\nthead {\n border: none;\n clip: rect(0 0 0 0"
},
{
"path": "src/assets/scripts/bundle/details.js",
"chars": 785,
"preview": "const container = document.querySelector('.details');\nconst expandAllButton = container.querySelector('#expandAll');\ncon"
},
{
"path": "src/assets/scripts/bundle/dialog.js",
"chars": 2311,
"preview": "// manages the behavior of modal several dialogs on a page: open / close buttons and light dismiss.\n\nconst modals = Arra"
},
{
"path": "src/assets/scripts/bundle/is-land.js",
"chars": 76,
"preview": "import '@11ty/is-land/is-land';\nimport '@11ty/is-land/is-land-autoinit.js';\n"
},
{
"path": "src/assets/scripts/bundle/nav-drawer.js",
"chars": 1222,
"preview": "// © Manuel Matuzović: https://web.dev/website-navigation/ / Web Accessibility Cookbook\n\nconst nav = document.querySelec"
},
{
"path": "src/assets/scripts/bundle/nav-sub.js",
"chars": 1578,
"preview": "const nav = document.querySelector('nav');\nconst navBreakpoint = '{{ designTokens.viewports.navigation }}';\n\n// toggle s"
},
{
"path": "src/assets/scripts/bundle/theme-toggle.js",
"chars": 2105,
"preview": "const storageKey = 'theme-preference';\nconst themeColors = {\n dark: '{{ meta.themeLight }}',\n light: '{{ meta.themeDar"
},
{
"path": "src/assets/scripts/components/custom-masonry.js",
"chars": 1346,
"preview": "class CustomMasonry extends HTMLElement {\n constructor() {\n super();\n this.layoutMasonry = this.layoutMasonry.bin"
},
{
"path": "src/assets/scripts/outbound-tracking.js",
"chars": 598,
"preview": "/**\n * Outbound Link Tracking for Fathom Analytics\n * Tracks clicks on company website links\n */\n(function() {\n 'use st"
},
{
"path": "src/blog/2017-10-03-announcing-remote-in-tech-company.md",
"chars": 1575,
"preview": "---\ntitle: \"Announcing - RemoteInTech.Company\"\ndate: 2017-10-03\nauthor: dougaitken\nexcerpt: \"Announcing - RemoteInTech.C"
},
{
"path": "src/blog/2017-10-09-so-is-this-just-another-job-board.md",
"chars": 670,
"preview": "---\ntitle: \"So is this just another job board?\"\ndate: 2017-10-09\nauthor: dougaitken\nexcerpt: \"So is this just another jo"
},
{
"path": "src/blog/2017-12-06-popular-company-profiles-and-the-github-traffic-charts.md",
"chars": 2809,
"preview": "---\ntitle: \"Popular company profiles and the GitHub traffic charts\"\ndate: 2017-12-06\nauthor: dougaitken\nexcerpt: \"Popula"
},
{
"path": "src/blog/2017-12-28-highest-view-count.md",
"chars": 1465,
"preview": "---\ntitle: \"Highest view count\"\ndate: 2017-12-28\nauthor: dougaitken\nexcerpt: \"\"\ntags:\n\n---\n\nSeems like the end of the ye"
},
{
"path": "src/blog/2018-01-01-new-year-new-job.md",
"chars": 429,
"preview": "---\ntitle: \"New year, new job?\"\ndate: 2018-01-01\nauthor: dougaitken\nexcerpt: \"\"\ntags:\n - distributed work\n - job searc"
},
{
"path": "src/blog/2018-01-06-advice-from-a-constant-job-seeker.md",
"chars": 494,
"preview": "---\ntitle: \"Advice from a constant job seeker\"\ndate: 2018-01-06\nauthor: dougaitken\nexcerpt: \"\"\ntags:\n - job search\n - "
},
{
"path": "src/blog/2018-09-19-website-updates.md",
"chars": 6943,
"preview": "---\ntitle: \"Website updates\"\ndate: 2018-09-19\nauthor: jnylen0\nexcerpt: \"\"\ntags:\n\n---\n\nWhen I first got involved with the"
},
{
"path": "src/blog/2018-09-25-hacktoberfest-is-back.md",
"chars": 393,
"preview": "---\ntitle: \"Hacktoberfest is back!\"\ndate: 2018-09-25\nauthor: dougaitken\nexcerpt: \"\"\ntags:\n - hacktoberfest\n---\n\nThe pro"
},
{
"path": "src/blog/2026-01-13-the-big-redesign.md",
"chars": 3150,
"preview": "---\ntitle: \"The Big Redesign: A Fresh Look for Remote In Tech\"\ndate: 2026-01-13\nauthor: dougaitken\nexcerpt: \"After years"
},
{
"path": "src/blog/2026-01-17-seo-improvements.md",
"chars": 4169,
"preview": "---\ntitle: \"Under the Hood: SEO Improvements for Better Discoverability\"\ndate: 2026-01-17\nauthor: dougaitken\nexcerpt: \"A"
},
{
"path": "src/blog/2026-02-15-keeping-things-tidy.md",
"chars": 3568,
"preview": "---\ntitle: \"Keeping Things Tidy: Housekeeping and Quality of Life Updates\"\ndate: 2026-02-15\nauthor: dougaitken\nexcerpt: "
},
{
"path": "src/blog/blog.json",
"chars": 88,
"preview": "{\n \"layout\": \"post\",\n \"permalink\": \"/blog/{{ page.fileSlug }}/\",\n \"tags\": [\"blog\"]\n}\n"
},
{
"path": "src/common/404.njk",
"chars": 3865,
"preview": "---\ntitle: Not Found\nlayout: base\npermalink: /404.html\neleventyExcludeFromCollections: true\nexcludeFromSitemap: true\n---"
},
{
"path": "src/common/_redirects.njk",
"chars": 2485,
"preview": "---\npermalink: /_redirects\neleventyExcludeFromCollections: true\nexcludeFromSitemap: true\n---\n# Redirect old WordPress su"
},
{
"path": "src/common/carbon.njk",
"chars": 727,
"preview": "---\npermalink: /carbon.txt\neleventyExcludeFromCollections: true\nexcludeFromSitemap: true\n---\n[org]\ndisclosures = [\n{%- i"
},
{
"path": "src/common/feed-atom.njk",
"chars": 1143,
"preview": "---\npermalink: /feed.xml\neleventyExcludeFromCollections: true\nexcludeFromSitemap: true\n---\n<?xml version=\"1.0\" encoding="
},
{
"path": "src/common/feed-json.njk",
"chars": 1023,
"preview": "---\npermalink: /feed.json\neleventyExcludeFromCollections: true\nexcludeFromSitemap: true\n---\n{\n \"version\": \"https://json"
},
{
"path": "src/common/humans.njk",
"chars": 314,
"preview": "---\npermalink: /humans.txt\neleventyExcludeFromCollections: true\nexcludeFromSitemap: true\n---\n\n/* TEAM */ Developer: {{ m"
},
{
"path": "src/common/og-images.njk",
"chars": 2773,
"preview": "---\nfontDisplay: \"'Red Hat Display', Ubuntu\" # needed created in local development, font must be installed on your syste"
},
{
"path": "src/common/robots.njk",
"chars": 2907,
"preview": "---\npermalink: /robots.txt\neleventyExcludeFromCollections: true\nexcludeFromSitemap: true\n---\nUser-agent: *\nDisallow: /40"
},
{
"path": "src/common/site-manifest.njk",
"chars": 695,
"preview": "---\npermalink: /site.webmanifest\neleventyExcludeFromCollections: true\nexcludeFromSitemap: true\n---\n{\n \"name\": \"{{ meta."
},
{
"path": "src/common/sitemap.njk",
"chars": 731,
"preview": "---\npermalink: /sitemap.xml\neleventyExcludeFromCollections: true\nexcludeFromSitemap: true\n---\n<?xml version=\"1.0\" encodi"
},
{
"path": "src/common/tagList.njk",
"chars": 816,
"preview": "---\nlayout: tags\npagination:\n data: collections.tagList\n size: 1\n alias: tag\npermalink: /tags/{{ tag | slugify }}/\nel"
},
{
"path": "src/common/tags.njk",
"chars": 454,
"preview": "---\nlayout: tags\npermalink: /tags/index.html\neleventyComputed:\n title: '{{ meta.blog.tagPlural }}'\n---\n\n<div class=\"tag"
},
{
"path": "src/companies/10up.md",
"chars": 2189,
"preview": "---\ntitle: \"10up\"\nslug: 10up\nwebsite: https://10up.com/\ncareers_url: https://10up.com/careers/\nregion: europe\nremote_pol"
},
{
"path": "src/companies/15five.md",
"chars": 1177,
"preview": "---\ntitle: \"15Five\"\nslug: 15five\nwebsite: https://www.15five.com\ncareers_url: https://www.15five.com/careers/\nregion: am"
},
{
"path": "src/companies/1password.md",
"chars": 1064,
"preview": "---\ntitle: \"1Password\"\nslug: 1password\nwebsite: https://www.1password.com\ncareers_url: https://1password.com/jobs/\nregio"
},
{
"path": "src/companies/37signals.md",
"chars": 900,
"preview": "---\ntitle: \"37signals\"\nslug: 37signals\nwebsite: https://37signals.com\nregion: worldwide\nremote_policy: fully-remote\ncomp"
},
{
"path": "src/companies/3blocks.md",
"chars": 1291,
"preview": "---\ntitle: \"3Blocks\"\nslug: 3blocks\nwebsite: https://3blocks.io/\ncareers_url: https://www.linkedin.com/company/3blocks-io"
},
{
"path": "src/companies/42-technologies.md",
"chars": 842,
"preview": "---\ntitle: \"42 Technologies\"\nslug: 42-technologies\nwebsite: https://www.42technologies.com/\ncareers_url: https://angel.c"
},
{
"path": "src/companies/90-seconds.md",
"chars": 1212,
"preview": "---\ntitle: \"90 Seconds\"\nslug: 90-seconds\nwebsite: https://90seconds.com/\ncareers_url: https://90seconds.com/about/career"
},
{
"path": "src/companies/a-1-auto-transport.md",
"chars": 3545,
"preview": "---\ntitle: \"A-1 Auto Transport\"\nslug: a-1-auto-transport\nwebsite: https://a1autotransport.com\nregion: worldwide\nremote_p"
},
{
"path": "src/companies/abiturma.md",
"chars": 1409,
"preview": "---\ntitle: \"abiturma GmbH\"\nslug: abiturma-gmbh\nwebsite: https://www.abiturma.de/\ncareers_url: https://www.abiturma.de/jo"
},
{
"path": "src/companies/ably.md",
"chars": 2744,
"preview": "---\ntitle: \"Ably\"\nslug: ably\nwebsite: https://www.ably.io/\ncareers_url: http://jobs.ably.io\nregion: europe\nremote_policy"
},
{
"path": "src/companies/abstract.md",
"chars": 1028,
"preview": "---\ntitle: \"Abstract\"\nslug: abstract\nwebsite: https://abstractapi.com\nregion: worldwide\nremote_policy: fully-remote\ncomp"
},
{
"path": "src/companies/acquia.md",
"chars": 1678,
"preview": "---\ntitle: \"Acquia\"\nslug: acquia\nwebsite: https://www.acquia.com/\ncareers_url: https://www.acquia.com/careers/open-posit"
},
{
"path": "src/companies/activecampaign.md",
"chars": 696,
"preview": "---\ntitle: \"activecampaign\"\nslug: activecampaign\nwebsite: https://www.activecampaign.com/\ncareers_url: https://www.activ"
},
{
"path": "src/companies/ad-hoc.md",
"chars": 1011,
"preview": "---\ntitle: \"Ad Hoc\"\nslug: ad-hoc\nwebsite: https://www.adhocteam.us/\ncareers_url: https://adhocteam.us/join/\nregion: amer"
},
{
"path": "src/companies/adaface.md",
"chars": 984,
"preview": "---\ntitle: \"Adaface\"\nslug: adaface\nwebsite: https://www.adaface.com\ncareers_url: https://angel.co/company/adaface/jobs\nr"
},
{
"path": "src/companies/addstructure.md",
"chars": 668,
"preview": "---\ntitle: \"addstructure\"\nslug: addstructure\nwebsite: https://www.bazaarvoice.com\nregion: americas\nremote_policy: remote"
},
{
"path": "src/companies/adeva.md",
"chars": 2200,
"preview": "---\ntitle: \"Adeva\"\nslug: adeva\nwebsite: https://adevait.com/\ncareers_url: https://adevait.com/careers/job-openings\nregio"
},
{
"path": "src/companies/adzuna.md",
"chars": 1387,
"preview": "---\ntitle: \"Adzuna\"\nslug: adzuna\nwebsite: https://www.adzuna.co.uk/\ncareers_url: https://www.adzuna.co.uk/jobs/search?cm"
},
{
"path": "src/companies/aerolab.md",
"chars": 809,
"preview": "---\ntitle: \"Aerolab\"\nslug: aerolab\nwebsite: https://aerolab.co/\ncareers_url: http://careers.aerolab.co/\nregion: americas"
},
{
"path": "src/companies/aerostrat.md",
"chars": 1165,
"preview": "---\ntitle: \"Aerostrat\"\nslug: aerostrat\nwebsite: https://aerostratsoftware.com/\ncareers_url: https://aerostratsoftware.co"
},
{
"path": "src/companies/aestudio.md",
"chars": 1171,
"preview": "---\ntitle: \"AEStudio\"\nslug: aestudio\nwebsite: https://ae.studio/\ncareers_url: https://ae.studio/join-us\nregion: americas"
},
{
"path": "src/companies/aha.md",
"chars": 1259,
"preview": "---\ntitle: \"Aha!\"\nslug: aha\nwebsite: https://www.aha.io\ncareers_url: http://www.aha.io/company/careers/current-openings\n"
},
{
"path": "src/companies/aiir.md",
"chars": 1156,
"preview": "---\ntitle: \"Aiir\"\nslug: aiir\nwebsite: https://aiir.com/\ncareers_url: https://aiir.com/about/working-at-aiir/\nregion: eur"
},
{
"path": "src/companies/aim-india.md",
"chars": 1551,
"preview": "---\ntitle: \"AIM India\"\nslug: aim-india\nwebsite: https://www.aimincorp.com/\ncareers_url: https://www.aimincorp.com/carrer"
},
{
"path": "src/companies/airbyte.md",
"chars": 1337,
"preview": "---\ntitle: \"Airbyte\"\nslug: airbyte\nwebsite: https://airbyte.com/\ncareers_url: https://airbyte.com/careers\nregion: europe"
},
{
"path": "src/companies/airgarage.md",
"chars": 1542,
"preview": "---\ntitle: \"AirGarage\"\nslug: airgarage\nwebsite: https://www.airgarage.com/\ncareers_url: https://www.airgarage.com/career"
}
]
// ... and 839 more files (download for full content)
About this extraction
This page contains the full source code of the remoteintech/remote-jobs GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 1039 files (1.4 MB), approximately 394.6k tokens, and a symbol index with 32 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.