Skip to content

Offbeat

Live preview ->

Offbeat is a portfolio for a small creative studio or a solo designer who wants the site to show some personality. The first screen is a huge two-line headline on a soft pink and peach canvas, with two floating tags and three tilted project cards. A curved ticker runs below it. The headline, the ticker, the service path, the illustrations and every button share one accent colour: orange by default, any colour you set, and an accent picker with six presets.

The homepage continues with a studio statement that colours in letter by letter as you scroll, the selected projects, six services along a winding line that draws itself, a moving image gallery, stacked testimonials, the latest journal posts and an FAQ written as a chat. Every project gets its own case study page.

PropertyValue
TierPro ($49)
CategoryAgency / Personal Portfolio
FontsAnybody for headings, DM Sans for text, both bundled locally
PalettePeach canvas (#f8eee8), pink hero (#fbe4e3), cream cards (#fff9f3), near-black ink (#171715), one accent colour (#ff5400 by default, six presets in the accent picker, or any hex)
MotionGSAP and Lenis: hero entrance and card parallax, curved ticker, scroll-coloured statement, self-drawing service path, moving gallery, testimonial deck, FAQ disclosures
StylesPlain CSS in src/styles/global.css, with Tailwind CSS 4 wired in
ContentEvery string, project, service, article and form field in src/config/site.ts
LanguagesEnglish by default; every route has a matching language route
  • / - hero, ticker, studio statement, selected projects, service path, gallery, testimonials, journal preview and FAQ
  • /work/ and /work/[slug]/ - all projects with filters by service, and a case study per project
  • /about/ - the studio story, values and process
  • /blog/ and /blog/[slug]/ - the journal and three articles
  • /contact/ - the contact form beside a direct email address
  • /privacy/

The production build generates 13 static pages, including the 404 page.

  • Not just orange - an accent picker with six presets, or any colour from one value: headlines, buttons, tags, the ticker, the service path, the illustrations and the favicon follow it.
  • Projects that tell a story - each project is a case study page with its own palette, and the first three form the hero.
  • Motion with a point of view - a curved ticker, a statement that colours in as you read and a service path that draws itself, all with pause controls or reduced-motion fallbacks.
  • Studio or personal - switch the structured data between an organization and a person.

Set url in src/config/site.ts to your full public address, for example https://example.com, or set PUBLIC_SITE_URL when you build. Canonical URLs, social images, the sitemap and the structured data use it.

name, wordmark, tagline and description set the studio name, the header wordmark and the default page title and description. branding sets the favicon and the logo file, and metadata sets the default social image and the browser bar colour. primary is the Let’s talk button in the header, and availability holds the two short lines beside it.

Set appearance.accent to a 3- or 6-digit hex colour:

appearance: { accent: "#6546d7" },

Buttons, tags, links, the ticker, the service path, the service icons, the supplied hero, FAQ and contact illustrations and the favicon all follow it. Lighter tints and readable darker text shades are worked out for you. Project images, project palettes and the logo are separate files and colours, so change those yourself.

The accent picker is the small palette button in the corner. It offers six presets (orange, violet, cobalt, forest, rose and teal), remembers the choice across pages and reloads, and Reset goes back to appearance.accent. Its presets and labels are in previewAccentSwitcher: change the colours, keep it for your visitors, or set enabled: false to hide it.

structuredData.type is "Organization" for a studio or "Person" for one designer. Add your real profile links to structuredData.sameAs.

Add an entry to projects. Its slug creates /work/<slug>/. Each project has a title, a category (used by the work page filters), a sector, a year, a portrait image with alt text, a brand badge, colours for its case study (color, ink and palette), a headline, a description, the services, and the story paragraphs.

hero.projects lists the three slugs shown as tilted cards in the hero, and gallery.projects lists the projects in the moving gallery. Replace an image path and its alt text together.

services.items holds the six services on the homepage path. Each has a lead and a trail word, a description and an icon. The icon names are target, cube, browser, box, pencil and motion, drawn in src/components/ServiceArt.astro.

testimonials.items holds the quote cards. The demo quotes are labelled as samples on the page; replace them with real, approved quotes and change the note text, or set it to an empty string.

The contact form starts in demo mode: it checks what is typed and says plainly that nothing was sent. Choose a provider in forms.contact.delivery:

delivery: defineDelivery({ provider: "formspree", formId: "YOUR_FORM_ID" }),
// or { provider: "formsubmit", recipient: "you@example.com" }
// or { provider: "netlify", formName: "contact" }
// or { provider: "custom", endpoint: "/api/contact", encoding: "json" }

Custom endpoints accept json, multipart or urlencoded. Everything in this config is sent to the browser, so never put a secret key in it. Update the privacy page for your provider. Netlify detects forms when it builds the site, so deploy again after switching to it.

forms.contact.fields lists the fields in the order they appear. Each has a name, a label, a type (text, email, tel, url, select or textarea), and optional required, placeholder, autocomplete, maxLength, rows, fullWidth, hideLabel and options settings. The name is what your provider receives.

Add an entry to journal.posts. Its slug creates /blog/<slug>/. Each article has a title, a category, a date and a modified date in YYYY-MM-DD, a description, a cover image with alt text, an introduction, a pull quote and sections, each with a heading and paragraphs. The headings feed the table of contents. journal.homeCount sets how many articles the homepage shows, and journal.author sets the author in the structured data.

  1. Copy src/i18n/locales/template.ts to a new file, for example fr.ts, and translate what you need.
  2. Import it and register it in src/i18n/config.ts with its label, HTML language and direction.
  3. Run npm run check, npm test, npm run verify:i18n and npm run build.

Anything you leave out falls back to English. Lists such as projects, services and articles replace the English list as a whole, so copy every item before translating. Keep slugs the same in every language. Set direction: "rtl" for right-to-left languages.

The animation lives in src/scripts/, and main.ts starts it and cleans it up between pages. The ticker and the gallery have pause buttons. Reduced-motion settings turn off the scroll animation, the smooth scrolling and the moving ticker, and the content stays readable without JavaScript.

labels.credit in site.ts holds the “Created by aerolaunch.app” line in the footer. Edit it, or remove it from src/components/Footer.astro.

AGENTS.md describes where content lives, how routes and languages work, how the motion is cleaned up and the checks to run.