## 1. Quick start

Three steps and the page is live with working forms.

1. Open `fillup.php` in a plain text editor. Notepad is fine, Word is not.
2. Find `$TO` near the top and put your own email address there.
3. Upload the whole folder to your web host, keeping the folder structure.

Then send yourself one test enquiry from the page and check three things.

1. The email arrives, and is not in your spam folder.
2. A file appeared at `storage/enquiries-2026-01.csv` with your test in it.
3. Typing that CSV address into a browser gives 403 or 404, never a download.

Do the third one before you take real enquiries. That folder holds your customers' names, phone numbers and addresses. Section 7 explains what to do if the file downloads.

### What you need

A host that runs PHP 7.4 or newer, which is nearly all of them. Nothing to install, no Composer, no libraries, no account anywhere. The page itself is plain HTML and CSS and works on any host at all; only the forms need PHP.

## 2. What is in the folder

index.html the page itself
fillup.php your settings and wording. The only file you have to edit.
send.php receives both forms, checks them, saves them, emails them
css/style.css all styling, with every colour set at the top
js/main.js one file, no libraries
images/ every picture on the page, JPEG and WebP
storage/ where enquiries are saved. Read the note inside.
docs/ this documentation
README.txt the short version of this file

The two files you will touch most are `index.html` for wording on the page and `fillup.php` for wording in the emails. You should not need to open `send.php` at all.

## 3. The sections

Every block in `index.html` starts with a comment like `<!-- ===== 5. Services ===== -->`, so searching for the number takes you straight there.

# | Section | What it is for
--- | --- | ---
1 | Header | Sticky. Logo, section links, phone number, one button. Gains a shadow once the page scrolls.
2 | Hero | Full width photograph under a scrim. Headline, two sentences, two buttons.
3 | Quick form | Three fields, sitting on a white card that overlaps the bottom of the hero.
4 | Trust strip | Four short claims. Change them to things that are true of your business.
5 | Services | Six cards with drawn icons. No photographs, so it stays quick to load.
6 | Where we work | Three rows, picture and text alternating sides.
7 | Parallax band | A photograph that scrolls slower than the page, with a short statement over it.
8 | How a visit works | Four numbered steps joined by a line.
9 | What is included | A comparison table, three columns, one highlighted.
10 | Reviews | Three short quotes.
11 | Gallery | Nine photographs, one large, all of them open in a popup.
12 | The full form | Eight fields beside a photograph.
13 | Questions | Six questions that open and close.
14 | Closing | A second parallax band with one button.
15 | Footer | Copyright line and the credit.

### Removing a section

Delete the whole block, from its comment down to the closing `</section>`. Nothing else breaks, except that any link in the header pointing at it should go too. The one section you cannot delete is number 12, because every button on the page scrolls to it.

## 4. Colours and fonts

Every colour is a variable at the top of `css/style.css`. Change one there and it changes everywhere.

    :root {
      --ink:      #0F172A;   /* headings and body text */
      --ink-soft: #55677A;   /* second-rank text */
      --blue:     #0284C7;   /* buttons, links, numbers */
      --blue-dk:  #026AA2;   /* the same, pressed */
      --blue-lt:  #38BDF8;   /* highlights on dark backgrounds */
      --tint:     #F1F7FB;   /* the pale section background */
      --line:     #DCE7F0;   /* hairlines and borders */
      --dark:     #081828;   /* the colour every scrim is mixed from */
    }

Changing the main colour. Set `--blue` to your own, then set `--blue-dk` to a noticeably darker version of it and `--blue-lt` to a lighter one. Those two do the work on hover states and on dark backgrounds, so leaving them behind is what makes a recoloured template look wrong.

### Fonts

The page uses whatever sans-serif font the reader's computer already has, so there is nothing to download and nothing to slow the page down. To use a web font instead, add its `<link>` in the head of `index.html` and change the `font` line in the `body` rule.

### Rounded corners and width

`--r` and `--r-lg` set how round the cards and panels are. Set both to `0` for square corners throughout. `--wrap` is how wide the content runs, currently 1160 pixels.

## 5. The services and the plan table

### The six service cards

Each one is a small block of HTML in section 5. Copy one to add a seventh, or delete one to have five. The grid rearranges itself either way.

    <article class="card">
      <span class="card-ico" aria-hidden="true"> ...an SVG icon... </span>
      <h3>Servicing and cleaning</h3>
      <p>Filters, coils and drain line cleared...</p>
    </article>

The icons are drawn in the HTML rather than loaded as pictures, so they are sharp on every screen, they cost nothing to download, and they take their colour from `--blue` automatically. To change one, replace the `<svg>` with any other, and give it `fill="none" stroke="currentColor"` so it picks up the colour.

### Keeping the job names in step

This is the mistake people make. A job name appears in three places: the `<select>` in the hero form, the `<select>` in the main form, and the `data-service` attribute on the plan buttons. Rename it in one place only and a button quietly stops selecting the right thing, with no error to warn you. Search `index.html` for the old wording and change every match.

### The comparison table

Section 9 is an ordinary HTML table. Each row is one thing you include:

    <tr>
      <th scope="row">Written report after the visit</th>
      <td><span class="no">No</span></td>
      <td class="pl-best"><span class="yes">Yes</span></td>
      <td><span class="yes">Yes</span></td>
    </tr>

The words Yes and No inside the spans are for screen readers; sighted visitors see a tick or a dash. Keep them.

`class="pl-best"` marks the highlighted column, and it has to be on every cell of that column including the header, because a table cannot be styled by column any other way. To highlight a different column, move every `pl-best` across, and move the `<span class="pl-flag">` with it.

To drop the prices, delete the `<span class="pl-price">` and `<span class="pl-sub">` lines from the three header cells, and delete the note underneath the table.

To use four columns rather than three, add a `<th>` to the header row and a `<td>` to every body row. Raise the `min-width` on `.plans` in the stylesheet by about 200 pixels so the table still scrolls rather than squashes on a phone.

## 6. The two forms

There are two, and they are the same enquiry. The short one in the hero asks for three things; the long one near the bottom asks for eight. Both post to `send.php`, both write to the same CSV, and the form column in that file records which one it was.

Field | Name in the HTML | Hero form | Full form | Required
--- | --- | --- | --- | ---
Name | `name` | yes | yes | no
Phone | `phone` | yes | yes | yes
Email | `email` | no | yes | no
What they need | `service` | yes | yes | no
Kind of property | `place` | no | yes | no
Area or postcode | `area` | no | yes | no
Best time to call | `when` | no | yes | no
What it is doing | `message` | no | yes | no

Why the phone is the required one. A service visit gets arranged by phone. Asking for an email address as well is one more reason for somebody to give up halfway. If you would rather have the email, open `send.php`, find the two validation blocks near the comment that says so, and swap which one is allowed to be empty.

### The wording in the emails

All of it is in `fillup.php`, in seven numbered parts. Words in curly brackets are swapped for the real details when the message is sent:

`{name}` `{phone}` `{email}` `{service}` `{place}` `{area}` `{when}` `{message}` `{site}` `{date}`

Spell them exactly, in lower case. A token that is spelled wrong prints as written rather than vanishing, which is how you spot it.

### The customer's copy

It goes out only when the customer typed an email address, which plenty will not. Set `$SEND_COPY` to `false` to stop sending it at all.

### Spam

Three things run on every submission, and each form has its own pair of hidden fields.

- A honeypot. A field named `website`, positioned off the side of the screen. Nobody using the site ever sees it. Anything that fills it in is a robot.
- A speed check. The page stamps the time it loaded into the `ts` field. Anything sent within two and a half seconds of that was not typed by a person.
- A rate limit. Five submissions an hour from one address, set by `$MAX_PER_HOUR`. Addresses are stored hashed, so `storage/rate.json` holds no personal data.

A caught submission is shown the ordinary thank you rather than an error. Telling a robot it was caught only teaches whoever wrote it what to change. The details go to `storage/trapped-*.csv`, so a real person caught by mistake is never lost. Look in that file now and then.

Do not delete the hidden fields. Both forms carry `website` and `ts`. Remove them and your spam rises sharply within days.

## 7. Where enquiries are saved

Every enquiry is written to a CSV file before any email is attempted. An email can fail for a dozen reasons outside your control; a written file cannot. The worst an email problem can cost you is a notification, never the job.

storage/enquiries-2026-01.csv every enquiry, one file per month
storage/trapped-2026-01.csv what the spam traps stopped
storage/mail-failures.log written only when an email did not go out
storage/rate.json counts recent submissions, addresses hashed

Open the CSV in Excel or Google Sheets. It is written with a byte order mark so accented characters come out right.

### Keeping the folder private

There is an `.htaccess` file inside `storage/` that blocks web access, written in both the Apache 2.2 and the Apache 2.4 way so it works on either. That covers most shared hosting.

If your host runs nginx, .htaccess is ignored. Do one of these. Either move the `storage` folder above your web root and point `$LOG_DIR` in `fillup.php` at the new place, which is the better answer, or add a deny rule for `/storage/` to your nginx config.

Either way, after your site is live, type the address of one CSV file into a browser. You want 403 or 404. If it downloads, your customers' details are public.

## 8. If email does not arrive

The enquiry is already saved, so nothing is lost. What has failed is only the notification.

PHP's built-in mail function is unreliable on some shared hosting, and mail sent that way often lands in spam because nothing proves it came from you. The fix is to send through your own mailbox instead. Fill in the `$SMTP` block at the bottom of `fillup.php` and change `'enabled'` to `true`.

Line | What to put
--- | ---
`host` | Your outgoing mail server. Whoever gives you email tells you this. Usually `smtp.yourdomain.com`, `smtp.gmail.com` or `mail.yourhost.com`.
`port` | 587 in most cases. 465 only if your provider says so.
`secure` | `tls` goes with 587, `ssl` goes with 465. They come as a pair.
`user` | Your full email address, including the @ part. Not just the bit before it.
`pass` | That mailbox's password.

Gmail, Outlook and Yahoo will refuse your normal password. They make you create a separate "app password" in your account security settings, and that is what goes in `pass`. If signing in keeps failing, this is almost always the reason.

Also set `$FROM` near the top of `fillup.php` to the same address as `user`. Many providers refuse a message that claims to come from somewhere else.

The password sits in the file as ordinary text. Use a mailbox created for the website rather than your personal one, and never put a copy of `fillup.php` anywhere public.

### Would you rather use PHPMailer?

It drops in without changing anything else. Replace the body of the `send_mail()` function in `send.php` with your PHPMailer calls; every other part of the script carries on as it is. The built-in sender is here so that the template works with nothing installed, not because it is better.

## 9. Images

Every picture is in `images/`, twice: a `.jpg` that works everywhere and a `.webp` that is roughly half the size. The browser picks the smaller one it understands.

File | Size | Where it appears
--- | --- | ---
`hero` | 1600 x 900 | Behind the headline
`place-home`, `place-office`, `place-shop` | 900 x 620 | The three alternating rows
`band` | 1600 x 900 | The parallax band, section 7
`g1` to `g9` | 1200 x 900 | The gallery, and the popup
`form` | 900 x 700 | Beside the enquiry form
`close` | 1600 x 900 | The closing band
`top` | 144 x 144 | The back to top button

### Swapping a picture

1. Crop your photograph to the size in the table above.
2. Save it over the old file, keeping the same name.
3. If you cannot make a WebP, delete the matching `.webp` file and remove its `<source>` line. The JPEG then does the work on its own.

Keep the width and height attributes matching the real file. They are on every `<img>` tag. They tell the browser how much room to leave before the picture arrives, and getting them wrong makes the page jump about while it loads.

The photographs are placeholders. They are not covered by the licence on this template. Replace them with your own, or with pictures you have the right to use, before you publish.

### The logo

It is drawn in the HTML, not a picture file, so it is sharp everywhere and takes its colour from `--blue`. Replace the `<svg>` inside `<span class="logo-mark">` with your own, or swap the whole span for an `<img>` of your logo.

## 10. Gallery and popup

Nine photographs. The first one takes a two by two square, the rest fill in around it. Every tile is a real `<button>`, which means it works from the keyboard as well as the mouse.

    <button class="gal-item gal-big" type="button" data-cap="What the caption says">
      <picture>
        <source type="image/webp" srcset="images/g1.webp">
        <img src="images/g1.jpg" alt="..." width="1200" height="900" loading="lazy" decoding="async">
      </picture>
    </button>

- `data-cap` is the line printed under the photograph in the popup.
- `gal-big` makes a tile take the large square. Only put it on one.
- Add or remove tiles freely. The popup counts whatever it finds.

Inside the popup, the arrows move between photographs, the arrow keys on the keyboard do the same, Escape closes it, and a swipe works on a phone. While it is open the keyboard stays inside it, and when it closes the focus goes back to the tile you came from.

Why the popup opens instantly. It reuses the file the browser already chose and cached for the tile, rather than fetching a bigger one. If you want a larger picture in the popup, point `pic.src` in block 6 of `js/main.js` at a separate file instead.

## 11. Parallax and motion

The two photograph bands scroll slower than the page. It is done in JavaScript rather than CSS because the CSS way, `background-attachment: fixed`, does nothing at all on an iPhone.

The picture inside each band is 140% of the band's height and pulled up 20%, which leaves 20% of slack above and below. The script never moves it more than 18% of the band height, so an edge can never appear. To make the movement stronger, raise `DRIFT` in block 5 of `js/main.js`, and raise the `height` and `top` on `.band-pic` to match.

To turn parallax off completely, delete the `data-parallax` attribute from the two `<div class="band-pic">` tags. The photographs then sit still and nothing else changes.

### People who ask for less movement

Anyone whose computer is set to reduce motion gets no parallax and no smooth scrolling. The page checks `prefers-reduced-motion` once, at the top of the script, and again in the stylesheet.

## 12. Without JavaScript

Everything works. This is worth knowing because it is unusual.

Feature | With JavaScript | Without
--- | --- | ---
Both forms | Sent in the background, the form is replaced by a thank you | Posts normally, `send.php` answers with a page of its own
Field errors | Printed under the field that is wrong | Listed on the answer page
Questions | Open and close | Open and close, because they are `<details>`
Gallery | Opens a popup | Photographs still show, they simply do not enlarge
Parallax | The photograph drifts | The photograph sits still
Plan buttons | Preselect the job and scroll | Jump to the form
Header shadow, back to top | Appear on scroll | Not shown, nothing is lost

Nothing on the page is hidden by CSS and revealed by script, which is the usual reason a page arrives blank when a script fails to load.

## 13. Browsers

Chrome, Edge, Firefox and Safari, current versions and a good way back. There is no build step, no framework and no library, so there is nothing to keep up to date.

- WebP images fall back to JPEG on their own.
- `aspect-ratio` keeps pictures from squashing. An older browser that does not know it still shows the picture.
- The layout uses grid, which every browser has supported for years.
- The script is written in the older JavaScript style on purpose, so it runs without being compiled.

## 14. Troubleshooting

What you see | What it is
--- | ---
The page is blank after editing `fillup.php` | A missing quote mark or semicolon. Undo your last change. Use straight quotes, not curly ones.
The form says it sent, but no email came | Check `storage/`. If the enquiry is in the CSV, the form is fine and the email is the problem. Section 8.
Nothing appears in `storage/` | PHP cannot write there. Set the folder to 755, or 775 if your host needs it.
The email arrives in spam | Set `$FROM` to an address on your own domain, then turn on SMTP. Section 8.
Clicking a plan button does not choose the right job | The `data-service` wording no longer matches the start of any option in the list. Section 5.
The page jumps about while it loads | An image whose `width` and `height` attributes do not match the real file. Section 9.
An enquiry from a real person never arrived | Look in `storage/trapped-*.csv`. If they are in there, they were caught by a spam trap.
The CSV file downloads in a browser | Stop and fix this. Section 7.
The gallery popup opens on the wrong photograph | Two tiles carry `gal-big`. Only one should.

## 15. Licence

Use this template for personal, commercial or client work, on as many sites as you like. Do not resell or redistribute the template files themselves.

The photographs that came with it are placeholders and are not covered. Replace them before you publish.

Full terms: 7uptheme.com/license