# How Margo Works

## How Margo Works

**Skill level:** Everyone · **Time:** 10 minutes

Read this page once and the rest of the theme stops being a mystery. Margo is built from a few simple ideas that repeat everywhere.

### The Big Picture
Margo is a **static site**: when you run `npm run build`, it turns your text files into finished HTML pages in `dist/`. There is no database and no admin panel. Your files *are* the website.

```text
config.toml ──► site settings: name, URL, layouts, forms, contact links
menu.en.json ─► header and footer links
theme.css ────► colors, type scale, radii

ROUTE  src/pages/[...lang]/pricing.astro
  │    which sections, in which order
  │
  ├─ PAGE  src/content/pricing/english/-index.md
  │        title, SEO, page header, named blocks
  │
  └─ each SECTION
       ├─ default: sections/english/pricing-section.md
       ├─ + page block: pricingSection: { ... }
       └─ look: options.layout (many sections)

Base.astro wraps every page:
  head · header · page · call to action · footer · cookie banner · search
```

### 1. Routes Decide What A Page Shows
Each file in `src/pages/[...lang]/` is one page (a **route**). It lists the sections that page shows, top to bottom. This is the main homepage, `src/pages/[...lang]/index.astro`, shortened:

```astro
<Base {...homepageContent?.data} footerSpaceTop={false} hideCallToAction>
  <LayoutVariants section="hero" content={heroSection} />
  <LayoutVariants section="projects" content={projectsSection} />
  <LayoutVariants section="services" content={servicesSection} />
  <LayoutVariants section="stats" content={statsSection} />
  <!-- ...more sections... -->
</Base>
```

To **reorder** sections, move a line. To **remove** a section from one page, delete its line. The `[...lang]` folder name means one file builds every language: English at `/pricing/`, French at `/fr/pricing/`, Arabic at `/ar/pricing/`.

On the homepages, sections are drawn through `<LayoutVariants section="..." content={...} />`. Keep that wrapper when you move them.

### 2. Sections Have Default Content
Every section has a matching content file in `src/content/sections/<language>/`. For example, `PricingSection.astro` reads `pricing-section.md`. That file holds the section's default text, lists, buttons, and options, with comments that explain them.

Edit a section file and the change appears **everywhere that section is used**, unless a page overrides it. Set `enable: false` at the top of the file to hide the section on every page.

### 3. Pages Can Override Any Section
A page file can change a section **for that page only** with a **named block**. The main homepage uses blocks to pick its layouts and the four projects in its hero:

```yaml
# src/content/homepage/english/-index.md
---
heroSection:
  cards: ["ceramic-forms", "envelope-system", "stone-still-life", "colour-fan"]
  options:
    layout: "masthead" # stack | panel | photo | centered | masthead
---
```

Block names are the section name in camelCase: `heroSection`, `projectsSection`, `pricingSection`, `ctaSection`, and so on. They are the keys of `sectionsSchema` in `src/sections.schema.ts`. [Sections](/margo/customization/sections/) has the full list.

A block with a wrong name, for example `teamSection:` instead of `teamGridSection:`, is ignored without an error. Copy block names from the page file or from `sectionsSchema`.

<Aside type="caution" title="A block only works where the route uses it">
  Adding `gallerySection:` to the pricing page does nothing, because
  `pricing.astro` does not render the gallery section. Check the route file to
  see which blocks a page reads.
</Aside>

### 4. How Overrides Merge
When a page block meets the section's default content, Margo merges them with four rules:

| You write in the page block            | Result                                                         |
| -------------------------------------- | -------------------------------------------------------------- |
| A text or number (`title: "Hello"`)    | Replaces the default value                                     |
| An object (`button:`, `options:`)      | Merges key by key: only the keys you write change              |
| A list (`buttons:`, `cards:`, `items:`) | Replaces the **whole** list. Write every item you want to keep |
| An empty string (`description: ""`)    | **Clears** the value. Leave the key out to keep the default    |

So this block switches the call to action to another layout on one page and keeps all its text and buttons:

```yaml
ctaSection:
  options:
    layout: "split"
```

In page files that have a schema, each block is checked on its own, before the merge. Most fields are optional, but a `contactSection` block needs `title`, `description`, and `form` (with its `inputs` list), and a nested object keeps its own required fields (a `button` needs `label`).

### 5. Layouts Change The Look, Not The Content
Many sections can be drawn in more than one way. The layout is one option:

```yaml
options:
  layout: "panel" # the hero: stack | panel | photo | centered | masthead
```

The allowed values are always in the comment next to `layout:`. Without a value, a section uses the first one in that list. The content fields stay the same, so you can switch back and forth. Set the layout in the section file (every page) or in a page block (one page).

Open `/elements/` on your local site to see the layouts of the multi-layout sections and of the page header side by side. See [Section Layouts](/margo/customization/section-layouts/) for every value.

Projects work the same way: each project file picks its page template with `options.layout` (`default` for a case study, `split` for the facts beside the images, `gallery` for a photo series). See [Projects](/margo/customization/projects/).

### 6. Base Wraps Every Page
`src/layouts/Base.astro` adds the parts that every page shares:

- the `<head>`: title, description, Open Graph tags, structured data, fonts, and your `[head]` scripts
- the header in one of three layouts (`[settings.layout] header` in `config.toml`, or `headerLayout` in a page), with the menus, the language switch, and the light/dark button
- the call to action above the footer, from `call-to-action.md` and the page's `ctaSection` block. The main homepage hides it
- the footer in one of three layouts (`[settings.layout] footer`, or `footerLayout` in a page), from `src/content/sections/<language>/footer.md`, the menu files, `src/config/social.json`, and `src/i18n/<language>.json`. Every footer layout also has the Ask AI row (`[settings.pageActions]`) and the theme credit, editable in `config.toml` (`[settings.themeCredit]`)
- the cookie banner, the search window, the back-to-top button or the contact dock (`[settings.contactDock]`, or `contactDock` in a page), and the shared scripts and animations

### 7. Small Interface Text Lives In i18n Files
Labels that are not page content, such as the header button ("Let's talk"), form messages, and the footer copyright, live in `src/i18n/<language>.json`. Several `config.toml` labels are empty on purpose (`label = ""`): empty means "use the translated text from the i18n file". Fill them in only if your site has one language.

### 8. Config Is Converted Before Astro Reads It
Astro cannot read TOML, so `npm run dev`, `npm run build`, and `npm run astro-check` first convert `src/config/config.toml` into `.astro/config.generated.json`. While `npm run dev` is running, this happens every time you save `config.toml`, and the page reloads. Always edit the TOML file, never the generated JSON.

Restart `npm run dev` after you change `trailingSlash` or `[settings.multilingual]`, after you add a language, and after you change `src/config/fonts.json`: Astro reads those only when it starts.

### 9. Collections Hold The Repeated Content
A **collection** is a folder of similar entries, each one a file. Margo's collections are `projects`, `services`, `blog`, `author`, `team`, `testimonials`, `pricing`, `faq`, `contact`, `about-us`, `homepage`, `pages`, and `sections`. They are defined in `src/content.config.ts`, and most have a **schema**: the list of fields an entry may have. See [Collections](/margo/customization/collections/).

### 10. What Happens During A Build
<Steps>

1. `config.toml` is converted to JSON.
2. Astro checks the content collections that have a schema, including the named blocks in their page frontmatter (`sectionsSchema` in `src/sections.schema.ts`). A wrong value there stops the build with the file name and field. The `sections`, `contact`, `faq`, and `pricing` collections have no schema, so a wrong value in those files, such as a misspelled `layout`, is not reported. Check those pages in the browser.
3. Every route is rendered once per language into `dist/`. A page without its own `image` gets a generated Open Graph card.
4. `llms.txt`, `llms-full.txt`, the Markdown copies of pages, the sitemap, `robots.txt`, and the RSS feed are written.
5. Yellow warnings print for anything still a placeholder: `[site]` for the `https://example.com` site URL, and `[forms]` for a placeholder form address.
6. The search index is built from the finished pages, and draft pages are removed from the sitemap.

</Steps>

### Development vs. Production
Some things behave differently while you work with `npm run dev`:

| Feature                         | `npm run dev`                       | Built site (`npm run build`)          |
| ------------------------------- | ----------------------------------- | ------------------------------------- |
| Pages with `draft: true`        | Shown, so you can preview them      | Not built                             |
| Changes to `config.toml`        | Shown when you save. Restart after changing languages, `trailingSlash`, or fonts | Read at the start of every build |
| Site search (⌘K)                | Says the index is created when the site is built | Works. Test it with `npm run preview` |
| Placeholder form address        | A warning box inside the form, and a `[forms]` line in the terminal | A `[forms]` line in the build output |
| Site URL warning                | Not shown                           | Printed when the build finishes, while the URL is `example.com` |
| Open Graph cards for each page  | Not generated                       | Generated during the build            |
| Forms                           | Sent to the address in `config.toml`, so test with care | Same |

### Find Where Any Text Comes From
See a sentence on the site and want to change it? Search the whole project for it: in VS Code press <kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>F</kbd> (<kbd>⌘</kbd> + <kbd>Shift</kbd> + <kbd>F</kbd> on Mac) and paste a few words. It will be in one of these places, checked in this order:

1. The page's own file, in a named block (`src/content/<collection>/<language>/-index.md`, or `src/content/pages/<language>/` for single pages such as `/archive/`)
2. The section's default file (`src/content/sections/<language>/`), including `footer.md` for the footer copy
3. A collection entry (`src/content/projects/`, `services/`, `team/`, `blog/`, `author/`)
4. The interface text (`src/i18n/<language>.json`)
5. The menus (`src/config/menu.<language>.json`)
6. Site settings (`src/config/config.toml`)

If the same text appears in both a page block and a section file, the page block wins on that page.