init
This commit is contained in:
46
.htaccess
Normal file
46
.htaccess
Normal file
@@ -0,0 +1,46 @@
|
||||
# Enable rewrite engine
|
||||
RewriteEngine On
|
||||
|
||||
# Force HTTPS (handled by Pangolin/Cloudflare, but just in case)
|
||||
# RewriteCond %{HTTPS} off
|
||||
# RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# Remove trailing slashes (except for directories)
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)/$ /$1 [L,R=301]
|
||||
|
||||
# Security headers
|
||||
<IfModule mod_headers.c>
|
||||
Header set X-Content-Type-Options "nosniff"
|
||||
Header set X-Frame-Options "SAMEORIGIN"
|
||||
Header set X-XSS-Protection "1; mode=block"
|
||||
Header set Referrer-Policy "strict-origin-when-cross-origin"
|
||||
</IfModule>
|
||||
|
||||
# Prevent directory listing
|
||||
Options -Indexes
|
||||
|
||||
# Block access to hidden files
|
||||
<FilesMatch "^\.">
|
||||
Require all denied
|
||||
</FilesMatch>
|
||||
|
||||
# Cache static assets
|
||||
<IfModule mod_expires.c>
|
||||
ExpiresActive On
|
||||
ExpiresByType text/css "access plus 1 month"
|
||||
ExpiresByType application/javascript "access plus 1 month"
|
||||
ExpiresByType image/jpeg "access plus 1 year"
|
||||
ExpiresByType image/png "access plus 1 year"
|
||||
ExpiresByType image/svg+xml "access plus 1 year"
|
||||
ExpiresByType image/webp "access plus 1 year"
|
||||
ExpiresByType font/woff2 "access plus 1 year"
|
||||
</IfModule>
|
||||
|
||||
# Gzip compression
|
||||
<IfModule mod_deflate.c>
|
||||
AddOutputFilterByType DEFLATE text/html text/css application/javascript text/plain application/json image/svg+xml
|
||||
</IfModule>
|
||||
|
||||
# Custom error pages
|
||||
ErrorDocument 404 /404.php
|
||||
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
14
.idea/deployment.xml
generated
Normal file
14
.idea/deployment.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PublishConfigData" serverName="ionos - uovidiu.com" remoteFilesAllowedToDisappearOnAutoupload="false">
|
||||
<serverData>
|
||||
<paths name="ionos - uovidiu.com">
|
||||
<serverdata>
|
||||
<mappings>
|
||||
<mapping deploy="/" local="$PROJECT_DIR$" web="/" />
|
||||
</mappings>
|
||||
</serverdata>
|
||||
</paths>
|
||||
</serverData>
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/uovidiu.iml" filepath="$PROJECT_DIR$/.idea/uovidiu.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
24
.idea/php-docker-settings.xml
generated
Normal file
24
.idea/php-docker-settings.xml
generated
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="PhpDockerContainerSettings">
|
||||
<list>
|
||||
<map>
|
||||
<entry key="7e064064-f569-4fa2-87dd-a1bd0db5798f">
|
||||
<value>
|
||||
<DockerContainerSettings>
|
||||
<option name="version" value="1" />
|
||||
<option name="volumeBindings">
|
||||
<list>
|
||||
<DockerVolumeBindingImpl>
|
||||
<option name="containerPath" value="/opt/project" />
|
||||
<option name="hostPath" value="$PROJECT_DIR$" />
|
||||
</DockerVolumeBindingImpl>
|
||||
</list>
|
||||
</option>
|
||||
</DockerContainerSettings>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</list>
|
||||
</component>
|
||||
</project>
|
||||
34
.idea/php.xml
generated
Normal file
34
.idea/php.xml
generated
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCSFixerOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PHPCodeSnifferOptionsConfiguration">
|
||||
<option name="highlightLevel" value="WARNING" />
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpCodeSniffer">
|
||||
<phpcs_settings>
|
||||
<phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="7e064064-f569-4fa2-87dd-a1bd0db5798f" timeout="30000" />
|
||||
</phpcs_settings>
|
||||
</component>
|
||||
<component name="PhpStan">
|
||||
<PhpStan_settings>
|
||||
<phpstan_by_interpreter asDefaultInterpreter="true" interpreter_id="7e064064-f569-4fa2-87dd-a1bd0db5798f" timeout="60000" />
|
||||
</PhpStan_settings>
|
||||
</component>
|
||||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="Psalm">
|
||||
<Psalm_settings>
|
||||
<psalm_fixer_by_interpreter asDefaultInterpreter="true" interpreter_id="7e064064-f569-4fa2-87dd-a1bd0db5798f" timeout="60000" />
|
||||
</Psalm_settings>
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/uovidiu.iml
generated
Normal file
8
.idea/uovidiu.iml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
14
.idea/webServers.xml
generated
Normal file
14
.idea/webServers.xml
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="WebServers">
|
||||
<option name="servers">
|
||||
<webServer id="0386710e-a468-412b-a338-359f07683c43" name="ionos - uovidiu.com" url="http://uovidiu.com">
|
||||
<fileTransfer rootFolder="/root/portfolio" accessType="SFTP" host="87.106.99.10" port="22" sshConfigId="c49579e5-6b45-4cda-80e7-5a0d2a1ba815" sshConfig="root@87.106.99.10:22 key" keyPair="true">
|
||||
<advancedOptions>
|
||||
<advancedOptions dataProtectionLevel="Private" keepAliveTimeout="0" passiveMode="true" shareSSLContext="true" />
|
||||
</advancedOptions>
|
||||
</fileTransfer>
|
||||
</webServer>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
38
404.php
Normal file
38
404.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>404 — Ovidiu Ungureanu</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=DM+Sans:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="nav">
|
||||
<a href="/" class="nav-logo">OU</a>
|
||||
<div class="nav-links">
|
||||
<a href="/#about">About</a>
|
||||
<a href="/#services">Services</a>
|
||||
<a href="/portfolio">Work</a>
|
||||
<a href="/#contact">Contact</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<header class="hero" style="text-align: center; align-items: center;">
|
||||
<div class="hero-label">Error 404</div>
|
||||
<h1>Not<br>Found</h1>
|
||||
<p class="hero-sub" style="text-align: center;">This page doesn't exist. It probably never did.</p>
|
||||
<div class="hero-cta">
|
||||
<a href="/" class="btn">← Back Home</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<footer class="footer">
|
||||
<span>© <?php echo date('Y'); ?> Ovidiu Ungureanu</span>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
91
CLAUDE.md
Normal file
91
CLAUDE.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Role
|
||||
|
||||
Act as a World-Class Senior Creative Technologist specialising in automotive service industry web design. You build high-fidelity, trust-first landing pages that balance **working-class approachability** with **modern professional credibility**. Every element should communicate: "We know cars. We're honest. We've been here 25 years and we'll be here 25 more." Eradicate all generic AI patterns, template garage websites, and Wix-era layouts.
|
||||
|
||||
---
|
||||
|
||||
## Always Do First
|
||||
|
||||
- **Invoke the `frontend-design` skill** before writing any frontend code, every session, no exceptions.
|
||||
- **Check `brand_assets/`** before designing. It may contain logos, colour guides, style guides, or images. If assets exist, use them — do not use placeholders where real assets are available. If a logo is present, use it. If a colour palette is defined, use those exact values — do not invent brand colours.
|
||||
|
||||
---
|
||||
|
||||
## Project Overview
|
||||
|
||||
Personal portfolio website for Ovidiu Ungureanu (PHP/MySQL developer). Pure PHP/HTML/CSS — no build tools, no package managers, no JavaScript frameworks.
|
||||
|
||||
## Development
|
||||
|
||||
No build step required. Serve with any PHP-capable web server (Apache recommended).
|
||||
|
||||
**Local development:**
|
||||
Running using Herd. no extra steps.
|
||||
|
||||
Apache is required in production for `.htaccess` rules (`mod_rewrite`, `mod_headers`, `mod_expires`, `mod_deflate`).
|
||||
|
||||
### Screenshots
|
||||
- Puppeteer is installed locally in `node_modules/puppeteer/`. Chrome cache is at `~/.cache/puppeteer/`.
|
||||
- **Always screenshot from localhost:** `node screenshot.mjs http://localhost:3000`
|
||||
- Screenshots save automatically to `./temporary screenshots/screenshot-N.png` (auto-incremented, never overwritten).
|
||||
- Optional label suffix: `node screenshot.mjs http://localhost:3000 label` → saves as `screenshot-N-label.png`.
|
||||
- `screenshot.mjs` lives in the project root. Use it as-is.
|
||||
- After screenshotting, read the PNG from `temporary screenshots/` with the Read tool — Claude can see and analyse the image directly.
|
||||
- When comparing, be specific: "heading is 32px but reference shows ~24px", "card gap is 16px but should be 24px".
|
||||
- Check: spacing/padding, font size/weight/line-height, colours (exact hex), alignment, border-radius, shadows, image sizing.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Includes system
|
||||
|
||||
Shared partials live in `includes/`:
|
||||
|
||||
- `includes/header.php` — `<!DOCTYPE>`, `<head>`, nav. Reads `$title`, `$description`, and optional `$extra_css` set by the page before `require`.
|
||||
- `includes/footer.php` — `<footer>`, `</body>`, `</html>`.
|
||||
|
||||
Nav active state is detected automatically via `$_SERVER['REQUEST_URI']` — no manual flags needed.
|
||||
|
||||
**Page template pattern:**
|
||||
```php
|
||||
<?php
|
||||
$title = 'Page Title';
|
||||
$description = 'Meta description.';
|
||||
$extra_css = '/css/page-specific.css'; // optional
|
||||
require __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
<!-- page body -->
|
||||
<?php require __DIR__ . '/includes/footer.php'; ?>
|
||||
```
|
||||
|
||||
### Pages
|
||||
|
||||
- `index.php` — Homepage (hero, about, services, work CTA, contact)
|
||||
- `portfolio/index.php` — Portfolio index + Thompson Service Centre case study
|
||||
- `404.php` — Custom error page
|
||||
|
||||
### CSS
|
||||
|
||||
- `css/style.css` — Global styles, design tokens, nav, all homepage sections
|
||||
- `css/portfolio.css` — Portfolio index rows and case study layout (`.cs-*` classes)
|
||||
|
||||
### Design system
|
||||
|
||||
- **Colors:** `--black: #0a0a0a` · `--white: #f5f0eb` · `--grey: #888` · `--accent: #ff3b00`
|
||||
- **Fonts:** JetBrains Mono (`--mono`) for headings/labels/UI · DM Sans (`--sans`) for body copy
|
||||
- **Breakpoints:** 960px (case study reflow), 768px (mobile)
|
||||
|
||||
### Adding portfolio projects
|
||||
|
||||
Edit the `$projects` array in `portfolio/index.php` and add a corresponding `<article id="…">` case study block below it. Project screenshots go in `img/{project-slug}/`.
|
||||
|
||||
Case study image naming convention (see Thompson as reference):
|
||||
- `hero.png` — full-page screenshot
|
||||
- `booking-lookup.png`, `booking-confirm.png` — feature screenshots
|
||||
|
||||
### Security
|
||||
|
||||
All user-facing output uses `htmlspecialchars()`. `.htaccess` sets security headers, disables directory listing, and blocks hidden files. No `http://` URLs in source — all external resources load over HTTPS.
|
||||
175
css/portfolio.css
Normal file
175
css/portfolio.css
Normal file
@@ -0,0 +1,175 @@
|
||||
/* ========================================
|
||||
PORTFOLIO — extends style.css only
|
||||
======================================== */
|
||||
|
||||
.nav-links a.active {
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* — Project card link — */
|
||||
|
||||
.project-card-link {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.project-card-inner {
|
||||
transition: transform 0.1s, box-shadow 0.1s;
|
||||
}
|
||||
|
||||
.project-card-link:hover .project-card-inner {
|
||||
transform: translate(-2px, -2px);
|
||||
box-shadow: 10px 10px 0px var(--black);
|
||||
}
|
||||
|
||||
.project-card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.project-card-year {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.75rem;
|
||||
color: var(--grey-text);
|
||||
}
|
||||
|
||||
.project-card-cta {
|
||||
display: inline-block;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.8rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/* — Tags — */
|
||||
|
||||
.tags-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.62rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 0.3rem 0.65rem;
|
||||
background: var(--black);
|
||||
border: 2px solid var(--grey-dark);
|
||||
color: var(--grey-text);
|
||||
}
|
||||
|
||||
/* — Case study meta grid — */
|
||||
|
||||
.cs-meta-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.cs-meta-grid .service-item h3 {
|
||||
font-size: 0.95rem;
|
||||
text-transform: none;
|
||||
font-weight: 500;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
/* — Browser chrome — */
|
||||
|
||||
.browser-chrome {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.6rem 1rem;
|
||||
background: var(--black);
|
||||
border: var(--border-orange);
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.browser-chrome--sm {
|
||||
padding: 0.45rem 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.browser-dots {
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.browser-dots span {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.browser-dots span:first-child { background: #ff5f57; }
|
||||
.browser-dots span:nth-child(2) { background: #febc2e; }
|
||||
.browser-dots span:nth-child(3) { background: #28c840; }
|
||||
|
||||
.browser-url {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.65rem;
|
||||
color: var(--grey-text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* — Screenshots — */
|
||||
|
||||
.screenshot-hero-wrap {
|
||||
width: 70%;
|
||||
margin: 0 auto;
|
||||
border: var(--border-orange);
|
||||
box-shadow: var(--shadow-orange);
|
||||
}
|
||||
|
||||
.screenshot-hero-wrap .browser-chrome {
|
||||
border: none;
|
||||
border-bottom: 2px solid var(--accent);
|
||||
}
|
||||
|
||||
.screenshot-hero {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.screenshot-card {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.screenshot-screen {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: var(--border-orange);
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
RESPONSIVE
|
||||
======================================== */
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.cs-meta-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.screenshot-hero-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cs-meta-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
31
css/screenshot.mjs
Normal file
31
css/screenshot.mjs
Normal file
@@ -0,0 +1,31 @@
|
||||
import puppeteer from 'puppeteer';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const url = process.argv[2] || 'http://localhost:3000';
|
||||
const label = process.argv[3] || '';
|
||||
|
||||
const dir = path.join(__dirname, 'temporary screenshots');
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
|
||||
let n = 1;
|
||||
const name = () => label ? `screenshot-${n}-${label}.png` : `screenshot-${n}.png`;
|
||||
while (fs.existsSync(path.join(dir, name()))) n++;
|
||||
const filepath = path.join(dir, name());
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: true,
|
||||
args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'],
|
||||
});
|
||||
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
|
||||
await page.goto(url, { waitUntil: 'networkidle0', timeout: 30000 });
|
||||
// Allow GSAP + web fonts to settle
|
||||
await new Promise(r => setTimeout(r, 2500));
|
||||
await page.screenshot({ path: filepath, fullPage: false });
|
||||
await browser.close();
|
||||
|
||||
console.log(`Saved: ${filepath}`);
|
||||
317
css/style.css
Normal file
317
css/style.css
Normal file
@@ -0,0 +1,317 @@
|
||||
/* ========================================
|
||||
RESET & BASE (ORANGE & GRAY BRUTALIST)
|
||||
======================================== */
|
||||
|
||||
*, *::before, *::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* Industrial Palette */
|
||||
--black: #000000;
|
||||
--white: #ffffff;
|
||||
--grey-dark: #2b2d30; /* Slate Concrete */
|
||||
--grey-light: #3f4246; /* Industrial Surface */
|
||||
--grey-text: #b0b0b0;
|
||||
--accent: #ff5500; /* Safety Orange */
|
||||
--mono: 'JetBrains Mono', monospace;
|
||||
--sans: 'DM Sans', sans-serif;
|
||||
|
||||
/* Brutalist Variables */
|
||||
--border-thick: 4px solid var(--black);
|
||||
--border-orange: 4px solid var(--accent);
|
||||
--shadow: 8px 8px 0px var(--black);
|
||||
--shadow-orange: 8px 8px 0px var(--accent);
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--grey-dark);
|
||||
color: var(--white);
|
||||
line-height: 1.2; /* Tighter brutalist spacing */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
padding: 10px; /* Outer frame */
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--accent);
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
NAV - THE CONTROL PANEL
|
||||
======================================== */
|
||||
|
||||
.nav {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 2rem;
|
||||
background: var(--black);
|
||||
border: var(--border-orange);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.nav-logo {
|
||||
font-family: var(--mono);
|
||||
font-weight: 800;
|
||||
font-size: 1.25rem;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
HERO - IMPACT ZONE
|
||||
======================================== */
|
||||
|
||||
.hero {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 10rem 3rem 6rem;
|
||||
border: var(--border-thick);
|
||||
background: var(--grey-light);
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.hero-label {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.85rem;
|
||||
text-transform: uppercase;
|
||||
background: var(--accent);
|
||||
color: var(--black);
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
font-weight: 900;
|
||||
margin-bottom: 2rem;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-family: var(--mono);
|
||||
font-size: clamp(3rem, 10vw, 8rem);
|
||||
font-weight: 800;
|
||||
line-height: 0.85;
|
||||
letter-spacing: -0.05em;
|
||||
margin-bottom: 2rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.hero-sub {
|
||||
font-size: 1.25rem;
|
||||
color: var(--white);
|
||||
background: var(--black);
|
||||
padding: 1rem;
|
||||
max-width: 550px;
|
||||
margin-bottom: 3rem;
|
||||
border-left: 8px solid var(--accent);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
BUTTONS - INDUSTRIAL ACTUATORS
|
||||
======================================== */
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
font-family: var(--mono);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 900;
|
||||
text-transform: uppercase;
|
||||
padding: 1.2rem 2.5rem;
|
||||
background: var(--accent);
|
||||
color: var(--black);
|
||||
border: var(--border-thick);
|
||||
box-shadow: 4px 4px 0px var(--black);
|
||||
transition: transform 0.1s, box-shadow 0.1s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translate(-2px, -2px);
|
||||
box-shadow: 7px 7px 0px var(--black);
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
border-color: var(--white);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.btn-ghost:hover {
|
||||
background: var(--white);
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
SECTIONS - MODULE CONTAINERS
|
||||
======================================== */
|
||||
|
||||
.section {
|
||||
padding: 5rem 3rem;
|
||||
background: var(--grey-light);
|
||||
border: var(--border-thick);
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
background: var(--black);
|
||||
color: var(--accent);
|
||||
padding: 5px 15px;
|
||||
display: inline-block;
|
||||
margin-bottom: 3rem;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.large-text {
|
||||
font-size: 1.75rem;
|
||||
line-height: 1.3;
|
||||
margin-bottom: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.section-content p {
|
||||
color: var(--white);
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
SERVICES - GRID BLOCKS
|
||||
======================================== */
|
||||
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 2rem;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.service-item {
|
||||
background: var(--black);
|
||||
padding: 3rem;
|
||||
border: var(--border-orange);
|
||||
box-shadow: var(--shadow);
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.service-item:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.service-num {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.8rem;
|
||||
color: var(--black);
|
||||
background: var(--accent);
|
||||
padding: 2px 8px;
|
||||
display: inline-block;
|
||||
margin-bottom: 1.5rem;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.service-item h3 {
|
||||
font-family: var(--mono);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--accent);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.service-item p {
|
||||
font-size: 1rem;
|
||||
color: var(--grey-text);
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
CONTACT & FOOTER
|
||||
======================================== */
|
||||
|
||||
.contact-email {
|
||||
display: block;
|
||||
font-family: var(--mono);
|
||||
font-size: clamp(1.2rem, 5vw, 3.5rem);
|
||||
font-weight: 900;
|
||||
margin: 2rem 0;
|
||||
color: var(--white);
|
||||
text-decoration: underline;
|
||||
text-decoration-color: var(--accent);
|
||||
text-underline-offset: 8px;
|
||||
}
|
||||
|
||||
.contact-email:hover {
|
||||
color: var(--accent);
|
||||
background: var(--black);
|
||||
}
|
||||
|
||||
.footer {
|
||||
background: var(--black);
|
||||
border: var(--border-orange);
|
||||
color: var(--grey-text);
|
||||
padding: 2rem 3rem;
|
||||
margin-top: 4rem;
|
||||
}
|
||||
|
||||
/* ========================================
|
||||
RESPONSIVE
|
||||
======================================== */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.nav {
|
||||
top: 10px; left: 10px; right: 10px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.hero, .section {
|
||||
padding: 6rem 1.5rem 3rem;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.service-item {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
BIN
img/thompson/booking-confirm.png
Normal file
BIN
img/thompson/booking-confirm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 MiB |
BIN
img/thompson/booking-lookup.png
Normal file
BIN
img/thompson/booking-lookup.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 MiB |
BIN
img/thompson/hero.png
Normal file
BIN
img/thompson/hero.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 42 MiB |
7
includes/footer.php
Normal file
7
includes/footer.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<footer class="footer">
|
||||
<span>© <?php echo date('Y'); ?> Ovidiu Ungureanu</span>
|
||||
<span>Built with PHP, no JS frameworks were harmed.</span>
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
27
includes/header.php
Normal file
27
includes/header.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php echo htmlspecialchars($title); ?></title>
|
||||
<meta name="description" content="<?php echo htmlspecialchars($description); ?>">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&family=DM+Sans:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<?php if (!empty($extra_css)): ?>
|
||||
<link rel="stylesheet" href="<?php echo htmlspecialchars($extra_css); ?>">
|
||||
<?php endif; ?>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<?php $current = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); ?>
|
||||
<nav class="nav">
|
||||
<a href="/" class="nav-logo">OU</a>
|
||||
<div class="nav-links">
|
||||
<a href="/#about">About</a>
|
||||
<a href="/#services">Services</a>
|
||||
<a href="/portfolio"<?php echo ($current === '/portfolio' || $current === '/portfolio/') ? ' class="active"' : ''; ?>>Work</a>
|
||||
<a href="/#contact">Contact</a>
|
||||
</div>
|
||||
</nav>
|
||||
72
index.php
Normal file
72
index.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
$title = 'Ovidiu Ungureanu — Web Developer';
|
||||
$description = 'PHP & MySQL developer based in the UK. Custom web applications, ERP systems, and freelance web development.';
|
||||
require __DIR__ . '/includes/header.php';
|
||||
?>
|
||||
|
||||
<header class="hero">
|
||||
<div class="hero-label">Web Developer — UK</div>
|
||||
<h1>Ovidiu<br>Ungureanu</h1>
|
||||
<p class="hero-sub">I build web applications that work.<br>PHP, MySQL, clean code, no nonsense.</p>
|
||||
<div class="hero-cta">
|
||||
<a href="/portfolio" class="btn">View Work →</a>
|
||||
<a href="#contact" class="btn btn-ghost">Get in Touch</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section id="about" class="section">
|
||||
<div class="section-label">001 — About</div>
|
||||
<div class="section-content">
|
||||
<p class="large-text">I'm a PHP/MySQL developer based in the UK, building custom web applications and backend systems. I run a small web agency focused on delivering practical, well-built solutions.</p>
|
||||
<p>My work ranges from custom ERP platforms to freelance web projects. I care about performance, maintainability, and getting things done right the first time. No frameworks for the sake of frameworks. No unnecessary complexity.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="services" class="section">
|
||||
<div class="section-label">002 — Services</div>
|
||||
<div class="services-grid">
|
||||
<div class="service-item">
|
||||
<span class="service-num">01</span>
|
||||
<h3>Custom Web Applications</h3>
|
||||
<p>Bespoke PHP applications built to your exact requirements. Backend systems, admin panels, data management tools.</p>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<span class="service-num">02</span>
|
||||
<h3>Database Design & Optimisation</h3>
|
||||
<p>MySQL schema design, query optimisation, and performance tuning. Making slow systems fast.</p>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<span class="service-num">03</span>
|
||||
<h3>Website Development</h3>
|
||||
<p>Clean, responsive websites. HTML, CSS, JavaScript, Vue.js. Built to load fast and work everywhere.</p>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<span class="service-num">04</span>
|
||||
<h3>Legacy System Modernisation</h3>
|
||||
<p>Upgrading and refactoring existing PHP codebases. Improving what you already have without starting over.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section section-cta">
|
||||
<div class="section-label">003 — Work</div>
|
||||
<div class="section-content">
|
||||
<p class="large-text">Selected projects and client work.</p>
|
||||
<a href="/portfolio" class="btn">View Portfolio →</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="contact" class="section">
|
||||
<div class="section-label">004 — Contact</div>
|
||||
<div class="section-content">
|
||||
<p class="large-text">Got a project in mind?</p>
|
||||
<a href="mailto:hello@uovidiu.com" class="contact-email">hello@uovidiu.com</a>
|
||||
<div class="contact-details">
|
||||
<span>Peterborough, UK</span>
|
||||
<span>·</span>
|
||||
<span>Available for freelance</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require __DIR__ . '/includes/footer.php'; ?>
|
||||
149
portfolio/index.php
Normal file
149
portfolio/index.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
$title = 'Portfolio — Ovidiu Ungureanu';
|
||||
$description = 'Selected web development projects by Ovidiu Ungureanu.';
|
||||
$extra_css = '/css/portfolio.css';
|
||||
require __DIR__ . '/../includes/header.php';
|
||||
?>
|
||||
|
||||
<header class="hero">
|
||||
<div class="hero-label">Portfolio — Selected Work</div>
|
||||
<h1>Selected<br>Work</h1>
|
||||
<p class="hero-sub">Client projects and builds.</p>
|
||||
</header>
|
||||
|
||||
<!-- PROJECT INDEX -->
|
||||
<section class="section">
|
||||
<div class="section-label">001 — Projects</div>
|
||||
<?php
|
||||
$projects = [
|
||||
[
|
||||
'num' => '01',
|
||||
'title' => 'Thompson Service Centre',
|
||||
'type' => 'Website Redesign',
|
||||
'year' => '2025',
|
||||
'tags' => ['HTML', 'Tailwind CSS v4', 'GSAP'],
|
||||
'anchor' => 'thompson',
|
||||
],
|
||||
];
|
||||
foreach ($projects as $p): ?>
|
||||
<a href="#<?php echo $p['anchor']; ?>" class="project-card-link">
|
||||
<div class="service-item project-card-inner">
|
||||
<span class="service-num"><?php echo $p['num']; ?></span>
|
||||
<div class="project-card-head">
|
||||
<h3><?php echo htmlspecialchars($p['title']); ?></h3>
|
||||
<span class="project-card-year"><?php echo htmlspecialchars($p['year']); ?></span>
|
||||
</div>
|
||||
<p><?php echo htmlspecialchars($p['type']); ?></p>
|
||||
<div class="tags-row">
|
||||
<?php foreach ($p['tags'] as $tag): ?>
|
||||
<span class="tag"><?php echo htmlspecialchars($tag); ?></span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<span class="project-card-cta">View Case Study →</span>
|
||||
</div>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
|
||||
<!-- CASE STUDY: THOMPSON SERVICE CENTRE -->
|
||||
|
||||
<section id="thompson" class="section">
|
||||
<div class="section-label">Case Study — 01</div>
|
||||
<p class="large-text">Thompson<br>Service Centre</p>
|
||||
<div class="services-grid cs-meta-grid">
|
||||
<div class="service-item">
|
||||
<span class="service-num">Type</span>
|
||||
<h3>Website Redesign</h3>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<span class="service-num">Stack</span>
|
||||
<h3>HTML · Tailwind CSS v4 · GSAP</h3>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<span class="service-num">Client</span>
|
||||
<h3>Thompson Service Centre, Peterborough</h3>
|
||||
</div>
|
||||
<div class="service-item">
|
||||
<span class="service-num">Year</span>
|
||||
<h3>2025</h3>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="section-label">Project Overview</div>
|
||||
<div class="section-content">
|
||||
<p>Thompson Service Centre needed more than a website — they needed a digital presence that matched 25 years of earned trust. We built a high-fidelity landing page that puts credibility front and centre: a bold hero section, a trust bar loaded with real credentials, and service cards that communicate competence without the usual garage-site clichés. Every element was designed to make a first-time visitor feel like they'd already found their mechanic.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="section-label">Homepage</div>
|
||||
<div class="screenshot-hero-wrap">
|
||||
<div class="browser-chrome">
|
||||
<div class="browser-dots">
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<span class="browser-url">thompsonservicecentre.co.uk</span>
|
||||
</div>
|
||||
<img
|
||||
src="/img/thompson/hero.png"
|
||||
alt="Thompson Service Centre — Homepage"
|
||||
class="screenshot-hero"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="section-label">Booking Flow</div>
|
||||
<div class="services-grid">
|
||||
<div class="service-item screenshot-card">
|
||||
<span class="service-num">Vehicle Lookup</span>
|
||||
<div class="browser-chrome browser-chrome--sm">
|
||||
<div class="browser-dots">
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<span class="browser-url">thompsonservicecentre.co.uk/book</span>
|
||||
</div>
|
||||
<img
|
||||
src="/img/thompson/booking-lookup.png"
|
||||
alt="Vehicle registration lookup"
|
||||
class="screenshot-screen"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
<div class="service-item screenshot-card">
|
||||
<span class="service-num">Booking Form</span>
|
||||
<div class="browser-chrome browser-chrome--sm">
|
||||
<div class="browser-dots">
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<span class="browser-url">thompsonservicecentre.co.uk/book</span>
|
||||
</div>
|
||||
<img
|
||||
src="/img/thompson/booking-confirm.png"
|
||||
alt="Vehicle confirmed and booking form"
|
||||
class="screenshot-screen"
|
||||
loading="lazy"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="section-label">Craft & Execution</div>
|
||||
<div class="section-content">
|
||||
<p>Built with vanilla HTML, Tailwind CSS v4, and GSAP-powered scroll animations, the page loads fast and feels premium on every device. The design system — rooted in Workshop Navy and Safety Orange — draws from real automotive heritage rather than off-the-shelf templates. Noise overlays, layered gradients, and spring-eased micro-interactions give it a tactile warmth that sets it apart from every other garage site in the postcode.</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section section-cta">
|
||||
<div class="section-label">005 — Contact</div>
|
||||
<div class="section-content">
|
||||
<p class="large-text">Got a project in mind?</p>
|
||||
<a href="mailto:hello@uovidiu.com" class="btn">Get in Touch →</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require __DIR__ . '/../includes/footer.php'; ?>
|
||||
Reference in New Issue
Block a user