Skip to content

Commit 7a8c080

Browse files
committed
Add About and Advertising pages; integrate AdSense management and update routing
1 parent 8e1ea1e commit 7a8c080

File tree

16 files changed

+312
-18
lines changed

16 files changed

+312
-18
lines changed

index.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
<link rel="preconnect" href="https://fonts.googleapis.com" />
1111
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
1212
<link href="https://fonts.googleapis.com/css2?family=Lora:ital,wght@0,400;0,600;1,400&family=Playfair+Display:ital,wght@0,700;1,400&display=swap" rel="stylesheet" />
13-
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3051490042018197"
14-
crossorigin="anonymous"></script>
1513
<title>Vichea Nath — Personal bulletins</title>
1614
</head>
1715
<body>

public/ads.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
google.com, pub-3051490042018197, DIRECT, f08c47fec0942fa0

scripts/generate-sitemap.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ if (!fs.existsSync(distDir)) {
3636

3737
const staticRoutes = [
3838
{ path: '', priority: '1.0', changefreq: 'daily' },
39+
{ path: 'about', priority: '0.8', changefreq: 'monthly' },
3940
{ path: 'posts', priority: '0.9', changefreq: 'weekly' },
4041
{ path: 'projects', priority: '0.8', changefreq: 'weekly' },
4142
{ path: 'contact', priority: '0.7', changefreq: 'monthly' },
43+
{ path: 'advertising', priority: '0.5', changefreq: 'yearly' },
4244
{ path: 'privacy', priority: '0.4', changefreq: 'yearly' },
4345
{ path: 'publishing-policy', priority: '0.5', changefreq: 'yearly' },
4446
];

src/App.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
import Router from './router'
1+
import AdSenseBootstrap from './components/AdSenseBootstrap';
2+
import Router from './router';
23

34
export default function App() {
4-
return <Router />
5+
return (
6+
<>
7+
<AdSenseBootstrap />
8+
<Router />
9+
</>
10+
);
511
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useEffect } from 'react';
2+
import { ADSENSE_CLIENT, ADSENSE_ENABLED } from '../lib/site';
3+
4+
const ADSENSE_SCRIPT_ID = 'adsense-script';
5+
6+
export default function AdSenseBootstrap() {
7+
useEffect(() => {
8+
// Keep Google ad code off the page until regional consent tooling is ready.
9+
if (!ADSENSE_ENABLED) {
10+
document.getElementById(ADSENSE_SCRIPT_ID)?.remove();
11+
return;
12+
}
13+
14+
if (document.getElementById(ADSENSE_SCRIPT_ID)) {
15+
return;
16+
}
17+
18+
const script = document.createElement('script');
19+
script.id = ADSENSE_SCRIPT_ID;
20+
script.async = true;
21+
script.src = `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${ADSENSE_CLIENT}`;
22+
script.crossOrigin = 'anonymous';
23+
document.head.appendChild(script);
24+
}, []);
25+
26+
return null;
27+
}

src/components/Layout.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Link, Outlet, NavLink } from 'react-router-dom';
2-
import { Home, Newspaper, FolderGit2, Github, Mail } from 'lucide-react';
2+
import { Home, Newspaper, FolderGit2, Github, Mail, CircleUserRound } from 'lucide-react';
33
import profileData from '../content/profile.json';
44
import { SOURCE_REPO_URL } from '../lib/site';
55

@@ -28,6 +28,10 @@ export default function Layout() {
2828
<Home size={18} aria-hidden />
2929
<span>Home</span>
3030
</NavLink>
31+
<NavLink to="/about" className="nav__link">
32+
<CircleUserRound size={18} aria-hidden />
33+
<span>About</span>
34+
</NavLink>
3135
<NavLink to="/posts" className="nav__link">
3236
<Newspaper size={18} aria-hidden />
3337
<span>Posts</span>
@@ -48,6 +52,8 @@ export default function Layout() {
4852
<footer className="footer">
4953
<p className="footer__line">{profile.name} · original software notes and project updates</p>
5054
<div className="footer__links">
55+
<Link to="/about">About</Link>
56+
<Link to="/advertising">Advertising</Link>
5157
<Link to="/privacy">Privacy Policy</Link>
5258
<Link to="/publishing-policy">Publishing Policy</Link>
5359
<Link to="/contact">Contact</Link>

src/lib/site.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ export const LINKEDIN_URL = 'https://www.linkedin.com/in/vicheanath/';
99
export const GITHUB_URL = 'https://github.com/vicheanath';
1010
export const SOURCE_REPO_URL = 'https://github.com/vicheanath/vicheanath.github.io';
1111
export const ADSENSE_CLIENT = 'ca-pub-3051490042018197';
12+
export const ADSENSE_PUBLISHER_ID = ADSENSE_CLIENT.replace(/^ca-/, '');
13+
export const ADSENSE_ENABLED = false;

src/pages/About.tsx

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Link } from 'react-router-dom';
2+
import { Github, Linkedin } from 'lucide-react';
3+
import Seo from '../components/Seo';
4+
import profileData from '../content/profile.json';
5+
import { GITHUB_URL, SITE_NAME, SITE_URL, SOURCE_REPO_URL } from '../lib/site';
6+
7+
const profile = profileData as {
8+
name: string;
9+
headline: string;
10+
location: string;
11+
linkedInUrl: string;
12+
about?: string;
13+
topSkills?: string[];
14+
};
15+
16+
export default function About() {
17+
return (
18+
<section className="page">
19+
<Seo
20+
title={`About - ${SITE_NAME}`}
21+
description="About the site owner, what this blog publishes, and how the site is maintained."
22+
path="about"
23+
jsonLd={[
24+
{
25+
'@context': 'https://schema.org',
26+
'@type': 'AboutPage',
27+
name: `About - ${SITE_NAME}`,
28+
url: `${SITE_URL}/about`,
29+
description: 'Publisher and site information for the blog.',
30+
},
31+
{
32+
'@context': 'https://schema.org',
33+
'@type': 'Person',
34+
name: profile.name,
35+
jobTitle: profile.headline,
36+
homeLocation: {
37+
'@type': 'Place',
38+
name: profile.location,
39+
},
40+
sameAs: [profile.linkedInUrl, GITHUB_URL],
41+
url: SITE_URL,
42+
},
43+
]}
44+
/>
45+
46+
<header className="page__header">
47+
<p className="page__eyebrow">About</p>
48+
<h1 className="page__title">Publisher and site information</h1>
49+
<p className="page__intro">
50+
This is an independently maintained software engineering site published by {profile.name}.
51+
It focuses on original notes, project updates, and practical writing about .NET, frontend
52+
development, and software delivery.
53+
</p>
54+
</header>
55+
56+
<section className="page__section">
57+
<h2>Who runs the site</h2>
58+
<p>
59+
{profile.name} is a {profile.headline} based in {profile.location}. The site is used to publish
60+
original technical writing, document project work, and keep a clear public record of ownership,
61+
policies, and contact channels.
62+
</p>
63+
{profile.about && <p>{profile.about}</p>}
64+
{profile.topSkills && profile.topSkills.length > 0 && (
65+
<p>
66+
<strong>Focus areas:</strong> {profile.topSkills.join(' · ')}
67+
</p>
68+
)}
69+
</section>
70+
71+
<section className="page__section">
72+
<h2>What readers should expect</h2>
73+
<p>
74+
Articles are intended to be original, readable, and specific enough to help working engineers.
75+
The site is not a guest-post marketplace, a republishing network, or a collection of thin pages
76+
created only to host ads.
77+
</p>
78+
<p>
79+
When content needs correction or clarification, it should be updated. Site-wide disclosures are
80+
kept in the <Link to="/privacy">Privacy Policy</Link>,{' '}
81+
<Link to="/publishing-policy">Publishing Policy</Link>, and{' '}
82+
<Link to="/advertising">Advertising page</Link>.
83+
</p>
84+
</section>
85+
86+
<section className="page__section">
87+
<h2>Ownership and transparency</h2>
88+
<div className="page__link-grid">
89+
<a href={profile.linkedInUrl} target="_blank" rel="noopener noreferrer" className="page__card-link">
90+
<Linkedin size={18} aria-hidden />
91+
<span>
92+
<strong>LinkedIn</strong>
93+
<span>Professional profile for the site owner and publisher.</span>
94+
</span>
95+
</a>
96+
<a href={GITHUB_URL} target="_blank" rel="noopener noreferrer" className="page__card-link">
97+
<Github size={18} aria-hidden />
98+
<span>
99+
<strong>GitHub</strong>
100+
<span>Public code, project history, and engineering work.</span>
101+
</span>
102+
</a>
103+
<a href={SOURCE_REPO_URL} target="_blank" rel="noopener noreferrer" className="page__card-link">
104+
<Github size={18} aria-hidden />
105+
<span>
106+
<strong>Site source</strong>
107+
<span>Repository for the blog itself, including routes and policy pages.</span>
108+
</span>
109+
</a>
110+
</div>
111+
</section>
112+
</section>
113+
);
114+
}

src/pages/Advertising.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { Link } from 'react-router-dom';
2+
import Seo from '../components/Seo';
3+
import {
4+
ADSENSE_CLIENT,
5+
ADSENSE_ENABLED,
6+
ADSENSE_PUBLISHER_ID,
7+
SITE_NAME,
8+
SITE_URL,
9+
} from '../lib/site';
10+
11+
export default function Advertising() {
12+
return (
13+
<section className="page">
14+
<Seo
15+
title={`Advertising - ${SITE_NAME}`}
16+
description="Advertising standards, AdSense status, and monetization rules for the site."
17+
path="advertising"
18+
jsonLd={{
19+
'@context': 'https://schema.org',
20+
'@type': 'WebPage',
21+
name: `Advertising - ${SITE_NAME}`,
22+
url: `${SITE_URL}/advertising`,
23+
description: 'Advertising and monetization disclosures for the blog.',
24+
}}
25+
/>
26+
27+
<header className="page__header">
28+
<p className="page__eyebrow">Advertising</p>
29+
<h1 className="page__title">Advertising and AdSense standards</h1>
30+
<p className="page__intro">
31+
This page explains how ads are handled on the site, what standards apply to placement and reader
32+
experience, and how the current AdSense setup is being managed.
33+
</p>
34+
</header>
35+
36+
<section className="page__section">
37+
<h2>Current ad status</h2>
38+
{ADSENSE_ENABLED ? (
39+
<>
40+
<p>
41+
Google AdSense code is currently enabled for this site under publisher account{' '}
42+
<code>{ADSENSE_CLIENT}</code>.
43+
</p>
44+
<p>
45+
Ads should appear only in placements that are visually separate from navigation, body copy,
46+
and code examples.
47+
</p>
48+
</>
49+
) : (
50+
<>
51+
<p>
52+
Google AdSense is configured for this site, but ad code is currently disabled in the frontend
53+
until regional consent tooling and final placement review are ready.
54+
</p>
55+
<p>
56+
The associated publisher identifiers are <code>{ADSENSE_CLIENT}</code> and{' '}
57+
<code>{ADSENSE_PUBLISHER_ID}</code> for the site&apos;s <code>ads.txt</code> declaration.
58+
</p>
59+
</>
60+
)}
61+
</section>
62+
63+
<section className="page__section">
64+
<h2>Placement and click policy</h2>
65+
<p>
66+
Ads should never be disguised as navigation, download buttons, code samples, or article controls.
67+
Readers are not asked to click ads, refresh pages to generate ads, or interact with advertising
68+
in exchange for content access.
69+
</p>
70+
<p>
71+
Any future ad placements should stay clearly separated from content blocks, especially on mobile
72+
layouts where accidental taps are more likely.
73+
</p>
74+
</section>
75+
76+
<section className="page__section">
77+
<h2>Content and monetization standards</h2>
78+
<p>
79+
The site is intended to earn trust first and monetize second. Articles should remain original and
80+
useful on their own, with monetization treated as secondary to readability, clarity, and site
81+
credibility.
82+
</p>
83+
<p>
84+
The site should not publish scraped, spun, misleading, or ad-heavy pages created primarily to
85+
capture ad impressions.
86+
</p>
87+
</section>
88+
89+
<section className="page__section">
90+
<h2>Consent and privacy</h2>
91+
<p>
92+
If Google ads are enabled in the future, privacy and consent disclosures remain available on the{' '}
93+
<Link to="/privacy">Privacy Policy</Link>. The site should not turn ad serving back on until the
94+
required consent flow for applicable regions is ready.
95+
</p>
96+
</section>
97+
98+
<section className="page__section">
99+
<h2>Questions or concerns</h2>
100+
<p>
101+
If you notice a misleading placement, missing disclosure, or policy issue, use the{' '}
102+
<Link to="/contact">Contact page</Link> to report it.
103+
</p>
104+
</section>
105+
</section>
106+
);
107+
}

src/pages/Contact.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ export default function Contact() {
8181
<p>
8282
For advertising, cookies, and data handling details, read the <Link to="/privacy">Privacy Policy</Link>.
8383
For how content is written, corrected, and disclosed, read the{' '}
84-
<Link to="/publishing-policy">Publishing Policy</Link>.
84+
<Link to="/publishing-policy">Publishing Policy</Link>. For monetization and ad placement
85+
standards, read the <Link to="/advertising">Advertising page</Link>.
8586
</p>
8687
<p className="page__muted">{DEFAULT_DESCRIPTION}</p>
8788
</section>

0 commit comments

Comments
 (0)