Initial commit: Astro speaker site with Forgejo Actions CI/CD
Some checks are pending
Build and Deploy to Storj / build-and-deploy (push) Waiting to run

This commit is contained in:
Scott Scoble 2026-04-28 20:52:10 -07:00
commit 3705631510
41 changed files with 9096 additions and 0 deletions

View file

@ -0,0 +1,31 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"date": {
"type": "string",
"format": "date-time"
},
"tags": {
"default": [],
"type": "array",
"items": {
"type": "string"
}
},
"$schema": {
"type": "string"
}
},
"required": [
"title",
"description",
"date"
]
}

View file

@ -0,0 +1,37 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"title": {
"type": "string"
},
"premise": {
"type": "string"
},
"duration": {
"default": "45–60 min",
"type": "string"
},
"audience": {
"default": [],
"type": "array",
"items": {
"type": "string"
}
},
"takeaways": {
"default": [],
"type": "array",
"items": {
"type": "string"
}
},
"$schema": {
"type": "string"
}
},
"required": [
"title",
"premise"
]
}

View file

@ -0,0 +1 @@
export default new Map();

View file

@ -0,0 +1 @@
export default new Map();

170
.astro/content.d.ts vendored Normal file
View file

@ -0,0 +1,170 @@
declare module 'astro:content' {
export interface RenderResult {
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
headings: import('astro').MarkdownHeading[];
remarkPluginFrontmatter: Record<string, any>;
}
interface Render {
'.md': Promise<RenderResult>;
}
export interface RenderedContent {
html: string;
metadata?: {
imagePaths: Array<string>;
[key: string]: unknown;
};
}
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
export type CollectionKey = keyof DataEntryMap;
export type CollectionEntry<C extends CollectionKey> = Flatten<DataEntryMap[C]>;
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
export type ReferenceDataEntry<
C extends CollectionKey,
E extends keyof DataEntryMap[C] = string,
> = {
collection: C;
id: E;
};
export type ReferenceLiveEntry<C extends keyof LiveContentConfig['collections']> = {
collection: C;
id: string;
};
export function getCollection<C extends keyof DataEntryMap, E extends CollectionEntry<C>>(
collection: C,
filter?: (entry: CollectionEntry<C>) => entry is E,
): Promise<E[]>;
export function getCollection<C extends keyof DataEntryMap>(
collection: C,
filter?: (entry: CollectionEntry<C>) => unknown,
): Promise<CollectionEntry<C>[]>;
export function getLiveCollection<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter?: LiveLoaderCollectionFilterType<C>,
): Promise<
import('astro').LiveDataCollectionResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>
>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
entry: ReferenceDataEntry<C, E>,
): E extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getEntry<
C extends keyof DataEntryMap,
E extends keyof DataEntryMap[C] | (string & {}),
>(
collection: C,
id: E,
): E extends keyof DataEntryMap[C]
? string extends keyof DataEntryMap[C]
? Promise<DataEntryMap[C][E]> | undefined
: Promise<DataEntryMap[C][E]>
: Promise<CollectionEntry<C> | undefined>;
export function getLiveEntry<C extends keyof LiveContentConfig['collections']>(
collection: C,
filter: string | LiveLoaderEntryFilterType<C>,
): Promise<import('astro').LiveDataEntryResult<LiveLoaderDataType<C>, LiveLoaderErrorType<C>>>;
/** Resolve an array of entry references from the same collection */
export function getEntries<C extends keyof DataEntryMap>(
entries: ReferenceDataEntry<C, keyof DataEntryMap[C]>[],
): Promise<CollectionEntry<C>[]>;
export function render<C extends keyof DataEntryMap>(
entry: DataEntryMap[C][string],
): Promise<RenderResult>;
export function reference<
C extends
| keyof DataEntryMap
// Allow generic `string` to avoid excessive type errors in the config
// if `dev` is not running to update as you edit.
// Invalid collection names will be caught at build time.
| (string & {}),
>(
collection: C,
): import('astro/zod').ZodPipe<
import('astro/zod').ZodString,
import('astro/zod').ZodTransform<
C extends keyof DataEntryMap
? {
collection: C;
id: string;
}
: never,
string
>
>;
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
type InferEntrySchema<C extends keyof DataEntryMap> = import('astro/zod').infer<
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
>;
type ExtractLoaderConfig<T> = T extends { loader: infer L } ? L : never;
type InferLoaderSchema<
C extends keyof DataEntryMap,
L = ExtractLoaderConfig<ContentConfig['collections'][C]>,
> = L extends { schema: import('astro/zod').ZodSchema }
? import('astro/zod').infer<L['schema']>
: any;
type DataEntryMap = {
"blog": Record<string, {
id: string;
body?: string;
collection: "blog";
data: InferEntrySchema<"blog">;
rendered?: RenderedContent;
filePath?: string;
}>;
"talks": Record<string, {
id: string;
body?: string;
collection: "talks";
data: InferEntrySchema<"talks">;
rendered?: RenderedContent;
filePath?: string;
}>;
};
type ExtractLoaderTypes<T> = T extends import('astro/loaders').LiveLoader<
infer TData,
infer TEntryFilter,
infer TCollectionFilter,
infer TError
>
? { data: TData; entryFilter: TEntryFilter; collectionFilter: TCollectionFilter; error: TError }
: { data: never; entryFilter: never; collectionFilter: never; error: never };
type ExtractEntryFilterType<T> = ExtractLoaderTypes<T>['entryFilter'];
type ExtractCollectionFilterType<T> = ExtractLoaderTypes<T>['collectionFilter'];
type ExtractErrorType<T> = ExtractLoaderTypes<T>['error'];
type LiveLoaderDataType<C extends keyof LiveContentConfig['collections']> =
LiveContentConfig['collections'][C]['schema'] extends undefined
? ExtractDataType<LiveContentConfig['collections'][C]['loader']>
: import('astro/zod').infer<
Exclude<LiveContentConfig['collections'][C]['schema'], undefined>
>;
type LiveLoaderEntryFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractEntryFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderCollectionFilterType<C extends keyof LiveContentConfig['collections']> =
ExtractCollectionFilterType<LiveContentConfig['collections'][C]['loader']>;
type LiveLoaderErrorType<C extends keyof LiveContentConfig['collections']> = ExtractErrorType<
LiveContentConfig['collections'][C]['loader']
>;
export type ContentConfig = typeof import("../src/content.config.js");
export type LiveContentConfig = never;
}

2
.astro/types.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
/// <reference types="astro/client" />
/// <reference path="content.d.ts" />

View file

@ -0,0 +1,46 @@
name: Build and Deploy to Storj
on:
push:
branches: [main]
workflow_dispatch:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Install rclone
run: |
curl https://rclone.org/install.sh | bash
- name: Configure rclone
run: |
mkdir -p ~/.config/rclone
cat > ~/.config/rclone/rclone.conf << 'EOF'
[storj]
type = s3
provider = Storj
access_key_id = ${{ secrets.STORJ_ACCESS_KEY }}
secret_access_key = ${{ secrets.STORJ_SECRET_KEY }}
endpoint = https://gateway.storjshare.io
EOF
- name: Deploy to Storj
run: |
rclone sync ./dist/ storj:${{ secrets.STORJ_BUCKET }}/ --progress
rclone sync ./public/downloads/ storj:${{ secrets.STORJ_BUCKET }}/downloads/ --progress

26
.gitignore vendored Normal file
View file

@ -0,0 +1,26 @@
# Dependencies
node_modules/
# Build output
dist/
# Environment
.env
.env.local
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
npm-debug.log*
# PDF output (compiled from .typ)
one-sheet.pdf

0
EOF Normal file
View file

64
README.md Normal file
View file

@ -0,0 +1,64 @@
# Scott Scoble — Speaker Website
Professional speaker/consultant website for Scott Scoble.
## Stack
- **Framework:** Astro 5
- **Hosting:** Storj (S3-compatible static hosting)
- **Domain:** scott.scoble.me
- **CI/CD:** Forgejo Actions
## Development
```bash
npm install
npm run dev
```
## Build
```bash
npm run build
```
Output goes to `dist/`.
## Deploy
Deployment is automatic on push to `main` via Forgejo Actions.
### Manual Setup
1. Add secrets to Forgejo repo:
- `STORJ_ACCESS_KEY` — Storj S3 access key
- `STORJ_SECRET_KEY` — Storj S3 secret key
- `STORJ_BUCKET` — Bucket name (e.g., `brand-site`)
2. Ensure bucket exists in Storj with public access and custom domain configured.
## PDF Generation
```bash
nix-shell -p typst --run "typst compile one-sheet.typ one-sheet.pdf"
```
## Project Structure
```
src/
components/ # Reusable Astro components
layouts/ # Page layouts
pages/ # Route pages
content/ # Content collections (future)
styles/ # Global CSS
public/
images/ # Static images
downloads/ # Downloadable files (PDFs)
.forgejo/
workflows/ # CI/CD workflows
```
## License
MIT

11
astro.config.mjs Normal file
View file

@ -0,0 +1,11 @@
// @ts-check
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
site: 'https://scott.scoble.me',
output: 'static',
build: {
inlineStylesheets: 'auto',
},
});

132
one-sheet.typ Normal file
View file

@ -0,0 +1,132 @@
#set page(
width: 8.5in,
height: 11in,
margin: 0.75in,
)
#set text(
font: "Libertinus Serif",
size: 11pt,
fill: rgb(45, 42, 36),
)
// Color palette
#let brown-dark = rgb(45, 42, 36)
#let brown = rgb(74, 68, 56)
#let green = rgb(61, 90, 62)
#let amber = rgb(200, 149, 46)
#let cream = rgb(250, 248, 245)
// Header
#align(center)[
#text(size: 28pt, weight: 600, fill: brown-dark)[Scott Scoble]
#v(6pt)
#text(size: 14pt, style: "italic", fill: brown)[
The human cultivation expert for the AI age
]
]
#v(24pt)
// Two column layout
#grid(
columns: (1fr, 1.8fr),
gutter: 24pt,
[
// Left column - photo placeholder
#align(center)[
#box(
width: 100%,
height: 1.8in,
fill: rgb(230, 228, 225),
radius: 4pt,
align(center + horizon)[
#text(size: 10pt, fill: brown)[Photo placeholder]
]
)
#v(16pt)
#text(size: 9pt, fill: brown)[
Keynote Speaker • Consultant • Author
]
#v(12pt)
#text(size: 10pt, weight: 600, fill: green)[Booking]
#v(4pt)
#text(size: 9pt)[
scott.scoble.me
scott"@"scoble.me
]
#v(12pt)
#text(size: 10pt, weight: 600, fill: green)[Audience]
#v(4pt)
#text(size: 9pt)[
Engineering leaders, CTOs, VP Engineering, People leaders
]
#v(12pt)
#text(size: 10pt, weight: 600, fill: green)[Format]
#v(4pt)
#text(size: 9pt)[
Keynotes: 45–60 min
Workshops: Half-day or full-day
]
]
],
[
// Right column - talks
#text(size: 13pt, weight: 600, fill: brown-dark)[Signature Talks]
#v(12pt)
#text(size: 11pt, weight: 600, fill: brown)[50 First Dates with AI]
#v(2pt)
#text(size: 9.5pt, fill: brown)[
Current AI memory architecture is anterograde amnesia — and we're all Henry Roth, recording VHS tapes every night so our agent can wake up functional tomorrow. This talk covers why your AI's "memory" is actually just replay, not learning, and the three-layer architecture that actually compounds.
]
#v(10pt)
#text(size: 11pt, weight: 600, fill: brown)[From Strip-Mining to Cultivation]
#v(2pt)
#text(size: 9.5pt, fill: brown)[
The shift from extraction to cultivation in AI-era teams. Why throughput optimization is killing your soil health. The most successful AI teams aren't the ones with the best models — they're the ones who stopped strip-mining their humans and started cultivating them.
]
#v(10pt)
#text(size: 11pt, weight: 600, fill: brown)[The Soil Under Your Team]
#v(2pt)
#text(size: 9.5pt, fill: brown)[
Diagnose what's actually depleted before you fertilize. A diagnostic framework for team health. Most team interventions fail because they prescribe before diagnosing. This workshop gives you a cultivation assessment framework that finds what's actually depleted.
]
#v(16pt)
#text(size: 11pt, weight: 600, fill: amber)[What People Say]
#v(6pt)
#text(size: 9pt, style: "italic", fill: brown)[
"Scott has a rare ability to diagnose team dynamics quickly and prescribe actionable solutions. His cultivation framework changed how we think about AI adoption."
]
#v(2pt)
#text(size: 8.5pt, fill: brown)[— Engineering Leader, Tech Company]
]
)
#v(1fr)
// Footer
#align(center)[
#text(size: 8pt, fill: brown)[
scott.scoble.me • scott"@"scoble.me
]
]

4736
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

17
package.json Normal file
View file

@ -0,0 +1,17 @@
{
"name": "brand-site-astro",
"type": "module",
"version": "0.0.1",
"engines": {
"node": ">=22.12.0"
},
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"astro": "^6.1.10"
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 B

4
public/favicon.svg Normal file
View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<rect width="100" height="100" rx="16" fill="#3d5a3e"/>
<text x="50" y="68" font-family="Georgia, serif" font-size="52" font-weight="bold" fill="#faf8f5" text-anchor="middle">S</text>
</svg>

After

Width:  |  Height:  |  Size: 258 B

View file

@ -0,0 +1,64 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice">
<!-- Simple dark background -->
<rect width="800" height="600" fill="#1a1815"/>
<!-- Subtle stage floor -->
<rect x="0" y="450" width="800" height="150" fill="#0d0c0b"/>
<!-- Spotlight circle on floor -->
<ellipse cx="400" cy="500" rx="180" ry="40" fill="#2a2620"/>
<!-- Audience silhouettes -- simple shapes -->
<g fill="#151412">
<!-- Row 1 -->
<circle cx="150" cy="420" r="20"/>
<circle cx="220" cy="415" r="22"/>
<circle cx="300" cy="418" r="21"/>
<circle cx="380" cy="412" r="23"/>
<circle cx="460" cy="416" r="22"/>
<circle cx="540" cy="419" r="21"/>
<circle cx="620" cy="422" r="20"/>
<circle cx="700" cy="425" r="19"/>
</g>
<g fill="#1c1915">
<!-- Row 2 (closer) -->
<circle cx="180" cy="380" r="24"/>
<circle cx="270" cy="375" r="26"/>
<circle cx="370" cy="372" r="27"/>
<circle cx="470" cy="376" r="26"/>
<circle cx="570" cy="380" r="25"/>
<circle cx="660" cy="384" r="24"/>
</g>
<!-- Speaker silhouette -->
<g fill="#0a0908">
<!-- Body/shoulders -->
<path d="M 340 520
L 340 420
Q 340 380, 360 350
L 360 320
Q 360 300, 380 290
L 420 290
Q 440 300, 440 320
L 440 350
Q 460 380, 460 420
L 460 520
Z"/>
<!-- Head -->
<ellipse cx="400" cy="260" rx="32" ry="38"/>
<!-- Left arm gesturing -->
<path d="M 360 330 Q 300 360 280 400" stroke="#0a0908" stroke-width="16" stroke-linecap="round" fill="none"/>
<!-- Right arm at side -->
<path d="M 440 330 Q 460 380 460 420" stroke="#0a0908" stroke-width="16" stroke-linecap="round" fill="none"/>
</g>
<!-- Simple spotlight from above -->
<polygon points="250,0 550,0 480,520 320,520" fill="#c8952e" opacity="0.08"/>
<!-- Subtle rim light on speaker -->
<ellipse cx="400" cy="520" rx="60" ry="15" fill="#c8952e" opacity="0.1"/>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,145 @@
---
// BookCard.astro — Book display card
export interface Props {
title: string;
description: string;
status?: 'published' | 'forthcoming' | 'concept';
purchaseLinks?: Array<{ label: string; url: string }>;
}
const { title, description, status = 'published', purchaseLinks = [] } = Astro.props;
const statusLabel: Record<string, string> = {
published: '',
forthcoming: 'Coming Soon',
concept: 'In Progress',
};
---
<article class="book-card card">
<div class="book-cover-placeholder" aria-hidden="true">
<span class="book-cover-title">{title}</span>
</div>
<div class="book-info">
<h3>{title}</h3>
{statusLabel[status] && (
<span class="book-status">{statusLabel[status]}</span>
)}
<p class="book-desc">{description}</p>
{purchaseLinks.length > 0 && (
<div class="book-links">
{purchaseLinks.map((link) => (
<a href={link.url} class="btn btn-outline" target="_blank" rel="noopener">{link.label}</a>
))}
</div>
)}
{status === 'forthcoming' && (
<form class="book-waitlist" action="https://formspree.io/f/placeholder" method="POST">
<input type="email" name="email" placeholder="Get notified when it's out" required aria-label="Email for waitlist" />
<button type="submit" class="btn btn-primary">Notify Me</button>
</form>
)}
</div>
</article>
<style>
.book-card {
display: flex;
gap: var(--space-xl);
align-items: flex-start;
text-align: left;
}
.book-cover-placeholder {
flex-shrink: 0;
width: 8rem;
height: 11rem;
background: var(--color-cream-warm);
border: 2px dashed var(--color-border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-sm);
}
.book-cover-title {
font-family: var(--font-heading);
font-size: var(--size-sm);
color: var(--color-text-muted);
text-align: center;
font-style: italic;
}
.book-info h3 {
margin-bottom: var(--space-xs);
}
.book-status {
display: inline-block;
font-size: var(--size-xs);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-amber);
background: rgba(200, 149, 46, 0.1);
padding: 0.15em 0.5em;
border-radius: 3px;
margin-bottom: var(--space-sm);
}
.book-desc {
color: var(--color-text-muted);
font-size: var(--size-sm);
margin-bottom: var(--space-md);
}
.book-links {
display: flex;
gap: var(--space-sm);
flex-wrap: wrap;
}
.book-waitlist {
display: flex;
gap: var(--space-sm);
margin-top: var(--space-md);
}
.book-waitlist input {
flex: 1;
padding: 0.5rem 0.75rem;
border: 1px solid var(--color-border);
border-radius: 4px;
font-family: var(--font-body);
font-size: var(--size-sm);
}
.book-waitlist input:focus {
outline: none;
border-color: var(--color-green);
}
@media (max-width: 639px) {
.book-card {
flex-direction: column;
align-items: center;
text-align: center;
}
.book-info {
text-align: center;
}
.book-links {
justify-content: center;
}
.book-waitlist {
flex-direction: column;
}
}
</style>

View file

@ -0,0 +1,52 @@
---
// ContactForm.astro — Reusable contact form
export interface Props {
formAction?: string;
showTopic?: boolean;
buttonLabel?: string;
}
const {
formAction = 'https://formspree.io/f/placeholder',
showTopic = true,
buttonLabel = 'Send Message',
} = Astro.props;
---
<form class="contact-form" action={formAction} method="POST">
<div class="form-group">
<label for="contact-name">Name</label>
<input type="text" id="contact-name" name="name" required autocomplete="name" />
</div>
<div class="form-group">
<label for="contact-email">Email</label>
<input type="email" id="contact-email" name="email" required autocomplete="email" />
</div>
{showTopic && (
<div class="form-group">
<label for="contact-topic">Topic</label>
<select id="contact-topic" name="topic">
<option value="">Select a topic</option>
<option value="speaking">Speaking Inquiry</option>
<option value="consulting">Consulting Inquiry</option>
<option value="general">General</option>
</select>
</div>
)}
<div class="form-group">
<label for="contact-message">Message</label>
<textarea id="contact-message" name="message" rows="5" required></textarea>
</div>
<button type="submit" class="btn btn-primary btn-large">{buttonLabel}</button>
</form>
<style>
.contact-form {
max-width: 36rem;
margin: 0 auto;
}
</style>

155
src/components/Footer.astro Normal file
View file

@ -0,0 +1,155 @@
---
const currentYear = new Date().getFullYear();
---
<footer class="site-footer">
<div class="footer-container">
<div class="footer-main">
<div class="footer-brand">
<span class="footer-logo">Scott Scoble</span>
<p class="footer-tagline">The human cultivation expert for the AI age.</p>
<div class="footer-social">
<a href="https://linkedin.com/in/scottscoble" target="_blank" rel="noopener" aria-label="LinkedIn">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
</a>
<a href="https://twitter.com/scottscoble" target="_blank" rel="noopener" aria-label="X/Twitter">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
</a>
<a href="https://github.com/sscoble" target="_blank" rel="noopener" aria-label="GitHub">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.605-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z"/></svg>
</a>
</div>
</div>
<div class="footer-links">
<div class="footer-col">
<h4>Pages</h4>
<a href="/">Home</a>
<a href="/speaking">Speaking</a>
<a href="/consulting">Consulting</a>
<a href="/about">About</a>
</div>
<div class="footer-col">
<h4>Resources</h4>
<a href="/books">Books</a>
<a href="/blog">Blog</a>
<a href="/contact">Contact</a>
</div>
</div>
</div>
<div class="footer-bottom">
<p>© {currentYear} Scott Scoble. All rights reserved.</p>
</div>
</div>
</footer>
<style>
.site-footer {
background: var(--color-brown-dark);
color: var(--color-cream);
padding: 4rem 0 2rem;
}
.footer-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
}
.footer-main {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 4rem;
padding-bottom: 3rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 2rem;
}
.footer-logo {
font-family: var(--font-heading);
font-size: 1.5rem;
font-weight: 600;
color: white;
display: block;
margin-bottom: 0.75rem;
}
.footer-tagline {
color: rgba(255, 255, 255, 0.7);
margin: 0 0 1.5rem;
font-size: 1rem;
}
.footer-social {
display: flex;
gap: 1rem;
}
.footer-social a {
color: rgba(255, 255, 255, 0.7);
transition: color 0.2s ease;
}
.footer-social a:hover {
color: var(--color-amber);
}
.footer-links {
display: flex;
gap: 3rem;
}
.footer-col {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.footer-col h4 {
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.1em;
color: rgba(255, 255, 255, 0.5);
margin: 0 0 0.5rem;
}
.footer-col a {
color: rgba(255, 255, 255, 0.8);
text-decoration: none;
font-size: 0.9375rem;
transition: color 0.2s ease;
}
.footer-col a:hover {
color: var(--color-amber);
}
.footer-bottom {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.875rem;
color: rgba(255, 255, 255, 0.5);
}
@media (max-width: 640px) {
.footer-main {
grid-template-columns: 1fr;
gap: 2rem;
}
.footer-links {
gap: 2rem;
}
.footer-bottom {
flex-direction: column;
gap: 1rem;
text-align: center;
}
}
</style>

150
src/components/Nav.astro Normal file
View file

@ -0,0 +1,150 @@
---
const navLinks = [
{ href: '/speaking', label: 'Speaking' },
{ href: '/consulting', label: 'Consulting' },
{ href: '/about', label: 'About' },
{ href: '/books', label: 'Books' },
{ href: '/blog', label: 'Blog' },
];
---
<nav class="site-nav" aria-label="Main navigation">
<div class="nav-container">
<a href="/" class="nav-logo">
<span class="logo-text">Scott Scoble</span>
</a>
<div class="nav-links">
{navLinks.map(link => (
<a href={link.href} class="nav-link">{link.label}</a>
))}
</div>
<a href="/speaking#book" class="nav-cta">Book Scott</a>
<button class="nav-menu-toggle" aria-label="Toggle menu" aria-expanded="false">
<span></span>
<span></span>
<span></span>
</button>
</div>
</nav>
<style>
.site-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
background: rgba(250, 248, 245, 0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--color-border-light);
z-index: 100;
}
.nav-container {
max-width: 1200px;
margin: 0 auto;
padding: 1rem 2rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.nav-logo {
display: flex;
align-items: center;
gap: 0.75rem;
text-decoration: none;
}
.logo-text {
font-family: var(--font-heading);
font-size: 1.5rem;
font-weight: 600;
color: var(--color-brown-dark);
}
.nav-links {
display: flex;
gap: 2.5rem;
}
.nav-link {
color: var(--color-brown);
text-decoration: none;
font-weight: 500;
font-size: 0.9375rem;
padding: 0.5rem 0;
position: relative;
transition: color 0.2s ease;
}
.nav-link::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 0;
height: 2px;
background: var(--color-amber);
transition: width 0.2s ease;
}
.nav-link:hover {
color: var(--color-brown-dark);
}
.nav-link:hover::after {
width: 100%;
}
.nav-cta {
background: var(--color-amber);
color: white;
padding: 0.625rem 1.5rem;
border-radius: 6px;
text-decoration: none;
font-weight: 600;
font-size: 0.9375rem;
transition: all 0.2s ease;
}
.nav-cta:hover {
background: var(--color-amber-light);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(200, 149, 46, 0.3);
}
.nav-menu-toggle {
display: none;
flex-direction: column;
gap: 4px;
background: none;
border: none;
padding: 0.5rem;
cursor: pointer;
}
.nav-menu-toggle span {
display: block;
width: 24px;
height: 2px;
background: var(--color-brown-dark);
transition: all 0.2s ease;
}
@media (max-width: 768px) {
.nav-links {
display: none;
}
.nav-cta {
display: none;
}
.nav-menu-toggle {
display: flex;
}
}
</style>

View file

@ -0,0 +1,69 @@
---
// NewsletterSignup.astro — Reusable newsletter signup block
export interface Props {
title?: string;
description?: string;
}
const {
title = 'Stay Cultivated',
description = 'Sign up for the newsletter. No spam. No AI-generated content. Just the soil report.',
} = Astro.props;
---
<div class="newsletter-block">
<h2>{title}</h2>
<p>{description}</p>
<form class="newsletter-form" action="https://formspree.io/f/placeholder" method="POST">
<label for="newsletter-email" class="sr-only">Email address</label>
<input id="newsletter-email" type="email" name="email" placeholder="your@email.com" required />
<button type="submit" class="btn btn-primary">Subscribe</button>
</form>
</div>
<style>
.newsletter-block {
text-align: center;
padding: var(--space-3xl) 0;
background: var(--color-cream-warm);
border-top: 1px solid var(--color-border-light);
}
.newsletter-block h2 {
margin-bottom: var(--space-sm);
}
.newsletter-block p {
color: var(--color-text-muted);
max-width: 28rem;
margin: 0 auto var(--space-lg);
}
.newsletter-form {
display: flex;
gap: var(--space-sm);
max-width: 24rem;
margin: 0 auto;
}
.newsletter-form input {
flex: 1;
padding: 0.75rem 1rem;
border: 1px solid var(--color-border);
border-radius: 4px;
font-family: var(--font-body);
font-size: var(--size-base);
}
.newsletter-form input:focus {
outline: none;
border-color: var(--color-green);
box-shadow: 0 0 0 3px rgba(61, 90, 62, 0.15);
}
@media (max-width: 639px) {
.newsletter-form {
flex-direction: column;
}
}
</style>

View file

@ -0,0 +1,120 @@
---
// PackageCard.astro — Consulting package card
export interface Props {
title: string;
tagline: string;
description: string;
deliverables: string[];
priceRange: string;
idealFor?: string;
}
const { title, tagline, description, deliverables, priceRange, idealFor } = Astro.props;
---
<article class="package-card card">
<h3>{title}</h3>
<p class="package-tagline">{tagline}</p>
<p class="package-desc">{description}</p>
<div class="package-deliverables">
<h4>What You Get</h4>
<ul>
{deliverables.map((d) => <li>{d}</li>)}
</ul>
</div>
{idealFor && (
<div class="package-ideal-for">
<h4>Ideal For</h4>
<p>{idealFor}</p>
</div>
)}
<div class="package-price">
<span>{priceRange}</span>
</div>
</article>
<style>
.package-card {
text-align: left;
display: flex;
flex-direction: column;
}
.package-card h3 {
font-size: var(--size-xl);
margin-bottom: var(--space-sm);
}
.package-tagline {
color: var(--color-amber);
font-weight: 600;
font-size: var(--size-sm);
text-transform: uppercase;
letter-spacing: 0.03em;
margin-bottom: var(--space-md);
}
.package-desc {
color: var(--color-text-muted);
font-size: var(--size-sm);
margin-bottom: var(--space-md);
flex: 1;
}
.package-deliverables {
margin-bottom: var(--space-lg);
}
.package-deliverables h4 {
font-family: var(--font-body);
font-size: var(--size-sm);
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-green);
margin-bottom: var(--space-sm);
}
.package-deliverables ul {
padding-left: var(--space-lg);
margin: 0;
}
.package-deliverables li {
font-size: var(--size-sm);
margin-bottom: var(--space-xs);
}
.package-ideal-for {
margin-bottom: var(--space-md);
}
.package-ideal-for h4 {
font-family: var(--font-body);
font-size: var(--size-sm);
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-amber);
margin-bottom: var(--space-xs);
}
.package-ideal-for p {
font-size: var(--size-sm);
color: var(--color-text-muted);
font-style: italic;
margin: 0;
}
.package-price {
text-align: center;
padding: var(--space-md);
background: var(--color-cream-warm);
border-radius: 6px;
font-family: var(--font-heading);
font-size: var(--size-md);
color: var(--color-brown);
margin-top: auto;
}
</style>

View file

@ -0,0 +1,85 @@
---
// PillarCard.astro — Home page pillar value card
export interface Props {
title: string;
description: string;
number: number;
}
const { title, description, number } = Astro.props;
---
<article class="pillar-card">
<span class="pillar-number" aria-hidden="true">{String(number).padStart(2, '0')}</span>
<div class="pillar-content">
<h3>{title}</h3>
<p>{description}</p>
</div>
<div class="pillar-accent"></div>
</article>
<style>
.pillar-card {
position: relative;
background: linear-gradient(135deg, #faf8f5 0%, #ffffff 100%);
border: 1px solid var(--color-border-light);
border-radius: 16px;
padding: 2.5rem;
overflow: hidden;
transition: all 0.3s ease;
}
.pillar-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.08);
border-color: var(--color-amber);
}
.pillar-number {
position: absolute;
top: 1.5rem;
right: 1.5rem;
font-family: var(--font-heading);
font-size: 4rem;
font-weight: 700;
color: var(--color-border);
opacity: 0.5;
line-height: 1;
}
.pillar-content {
position: relative;
z-index: 1;
}
.pillar-card h3 {
font-family: var(--font-heading);
font-size: 1.5rem;
color: var(--color-brown-dark);
margin: 0 0 1rem;
padding-right: 3rem;
}
.pillar-card p {
color: var(--color-text-muted);
font-size: 1rem;
line-height: 1.7;
margin: 0;
}
.pillar-accent {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--color-amber) 0%, var(--color-green) 100%);
transform: scaleX(0);
transform-origin: left;
transition: transform 0.3s ease;
}
.pillar-card:hover .pillar-accent {
transform: scaleX(1);
}
</style>

View file

@ -0,0 +1,82 @@
---
// TalkCard.astro — Speaking page talk topic card
export interface Props {
title: string;
premise: string;
takeaways: string[];
audience: string;
slug?: string;
}
const { title, premise, takeaways, audience } = Astro.props;
---
<article class="talk-card card">
<h3>{title}</h3>
<p class="talk-premise">{premise}</p>
{takeaways.length > 0 && (
<div class="talk-takeaways">
<h4>Key Takeaways</h4>
<ul>
{takeaways.map((t) => <li>{t}</li>)}
</ul>
</div>
)}
<div class="talk-audience">
<span class="talk-label">Ideal Audience:</span> {audience}
</div>
</article>
<style>
.talk-card {
text-align: left;
}
.talk-card h3 {
font-size: var(--size-xl);
margin-bottom: var(--space-sm);
}
.talk-premise {
color: var(--color-text-muted);
font-size: var(--size-md);
margin-bottom: var(--space-lg);
font-style: italic;
}
.talk-takeaways {
margin-bottom: var(--space-md);
}
.talk-takeaways h4 {
font-family: var(--font-body);
font-size: var(--size-sm);
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--color-green);
margin-bottom: var(--space-sm);
}
.talk-takeaways ul {
padding-left: var(--space-lg);
}
.talk-takeaways li {
margin-bottom: var(--space-xs);
font-size: var(--size-sm);
}
.talk-audience {
padding-top: var(--space-md);
border-top: 1px solid var(--color-border-light);
font-size: var(--size-sm);
color: var(--color-text-muted);
}
.talk-label {
font-weight: 600;
color: var(--color-brown);
}
</style>

View file

@ -0,0 +1,69 @@
---
// TestimonialCard.astro — Placeholder testimonial card
export interface Props {
quote: string;
name: string;
role: string;
organization?: string;
}
const { quote, name, role, organization } = Astro.props;
---
<figure class="testimonial-card card">
<blockquote>
<p>&ldquo;{quote}&rdquo;</p>
</blockquote>
<figcaption>
<strong>{name}</strong>
<span>{role}{organization ? `, ${organization}` : ''}</span>
</figcaption>
</figure>
<style>
.testimonial-card {
text-align: left;
}
.testimonial-card blockquote {
border: none;
padding: 0;
margin: 0 0 var(--space-md);
background: none;
font-style: normal;
}
.testimonial-card blockquote p {
font-size: var(--size-sm);
line-height: 1.7;
color: var(--color-text);
position: relative;
}
.testimonial-card blockquote p::before {
content: '"';
font-family: var(--font-heading);
font-size: var(--size-2xl);
color: var(--color-amber);
position: absolute;
top: -0.25em;
left: -0.5em;
}
.testimonial-card figcaption {
border-top: 1px solid var(--color-border-light);
padding-top: var(--space-md);
}
.testimonial-card figcaption strong {
display: block;
font-size: var(--size-sm);
color: var(--color-brown);
}
.testimonial-card figcaption span {
display: block;
font-size: var(--size-xs);
color: var(--color-text-muted);
}
</style>

25
src/content.config.ts Normal file
View file

@ -0,0 +1,25 @@
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
}),
});
const talks = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/talks' }),
schema: z.object({
title: z.string(),
premise: z.string(),
duration: z.string().default('45–60 min'),
audience: z.array(z.string()).default([]),
takeaways: z.array(z.string()).default([]),
}),
});
export const collections = { blog, talks };

View file

@ -0,0 +1,112 @@
---
// BaseLayout.astro — Base layout with nav, footer, SEO meta
import '../styles/global.css';
import Nav from '../components/Nav.astro';
import Footer from '../components/Footer.astro';
export interface Props {
title: string;
description: string;
ogImage?: string;
ogType?: string;
canonicalUrl?: string;
}
const {
title,
description,
ogImage = '/social-card.png',
ogType = 'website',
canonicalUrl,
} = Astro.props;
const siteTitle = 'Scott Scoble';
const fullTitle = title.includes(siteTitle) ? title : `${title} — ${siteTitle}`;
const siteUrl = 'https://scott.scoble.me';
const currentPath = canonicalUrl || Astro.url.pathname;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="generator" content={Astro.generator} />
<!-- Primary Meta -->
<title>{fullTitle}</title>
<meta name="description" content={description} />
<meta name="author" content="Scott Scoble" />
<!-- Open Graph -->
<meta property="og:title" content={fullTitle} />
<meta property="og:description" content={description} />
<meta property="og:type" content={ogType} />
<meta property="og:url" content={new URL(currentPath, siteUrl).href} />
<meta property="og:image" content={new URL(ogImage, siteUrl).href} />
<meta property="og:site_name" content="Scott Scoble" />
<meta property="og:locale" content="en_US" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={fullTitle} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={new URL(ogImage, siteUrl).href} />
<!-- Canonical -->
<link rel="canonical" href={new URL(currentPath, siteUrl).href} />
<!-- RSS -->
<link rel="alternate" type="application/rss+xml" title="Scott Scoble Blog" href="/rss.xml" />
<!-- Google Fonts -->
<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=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&display=swap" rel="stylesheet" />
</head>
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<Nav currentPath={currentPath} />
<main id="main-content">
<slot />
</main>
<Footer />
</body>
</html>
<style is:global>
/* CSS Variables must be defined globally for all components to access */
:root {
--color-brown: #4a4438;
--color-brown-dark: #2d2a24;
--color-green: #3d5a3e;
--color-green-dark: #2d5a3e;
--color-cream: #faf8f5;
--color-cream-warm: #f5f0e8;
--color-amber: #c8952e;
--color-amber-light: #d4a94e;
--color-text: #2d2a24;
--color-text-muted: #6b6458;
--color-border: #e0dbd2;
--color-border-light: #ede9e2;
--color-bg: #faf8f5;
--color-bg-alt: #f5f0e8;
--font-heading: 'EB Garamond', Georgia, serif;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--size-xs: 0.75rem;
--size-sm: 0.875rem;
--size-base: 1rem;
--size-md: 1.125rem;
--size-lg: 1.25rem;
--size-xl: 1.5rem;
--size-2xl: 2rem;
--size-3xl: 2.5rem;
--size-4xl: 3rem;
}
</style>

75
src/pages/404.astro Normal file
View file

@ -0,0 +1,75 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout
title="Page Not Found — Scott Scoble"
description="That page doesn't exist."
>
<section class="error-page">
<div class="error-content">
<span class="error-code">404</span>
<h1>Page not found</h1>
<p>That link doesn't lead anywhere. Maybe it moved, maybe it never existed.</p>
<a href="/" class="btn">Go home</a>
</div>
</section>
</BaseLayout>
<style>
.error-page {
min-height: 70vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 6rem 2rem;
background: var(--color-cream);
}
.error-content {
max-width: 500px;
}
.error-code {
display: block;
font-family: var(--font-heading);
font-size: 6rem;
font-weight: 700;
color: var(--color-amber);
opacity: 0.3;
line-height: 1;
margin-bottom: 1rem;
}
.error-content h1 {
font-family: var(--font-heading);
font-size: 2rem;
color: var(--color-brown-dark);
margin: 0 0 1rem;
}
.error-content p {
color: var(--color-text-muted);
margin: 0 0 2rem;
line-height: 1.6;
}
.btn {
display: inline-block;
background: var(--color-amber);
color: white;
padding: 0.875rem 2rem;
border-radius: 6px;
text-decoration: none;
font-weight: 600;
transition: all 0.2s ease;
}
.btn:hover {
background: var(--color-amber-light);
transform: translateY(-1px);
}
</style>

221
src/pages/about.astro Normal file
View file

@ -0,0 +1,221 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout
title="About — Scott Scoble"
description="Scott Scoble is a builder, writer, and speaker focused on the human side of AI systems. Author of The Soil Beneath the Code and A Walk in Mentoring."
>
<section class="hero">
<div class="hero-content">
<h1>About</h1>
</div>
</section>
<section class="bio-section">
<div class="section-container">
<div class="bio-content">
<p class="bio-lead">
Scott Scoble is a builder, writer, and speaker focused on the human side of AI systems.
He's the author of <em>The Soil Beneath the Code</em> and <em>A Walk in Mentoring</em>,
and creator of Speckl — a dark provenance system for agentic engineering.
He advises engineering leaders on how to build AI systems that cultivate human capability
instead of strip-mining it.
</p>
<h2>The Cultivation Framework</h2>
<div class="framework-grid">
<div class="framework-item">
<h3>Soil Assessment</h3>
<p>Diagnose before prescribing. Know what's actually depleted before you fertilize.</p>
</div>
<div class="framework-item">
<h3>Formation</h3>
<p>Cultivate capability, don't extract output. Build systems that get stronger with use.</p>
</div>
<div class="framework-item">
<h3>Accountability</h3>
<p>Hold the line on human infrastructure. Dark provenance for every agent output.</p>
</div>
</div>
</div>
<div class="photo-placeholder">
<p>📷 Photo coming soon</p>
</div>
</div>
</section>
<section class="books-section">
<div class="section-container">
<h2>Books</h2>
<div class="books-grid">
<article class="book-card">
<h3>The Soil Beneath the Code</h3>
<p class="book-status">Published</p>
<p>What engineering teams can learn from farmers about sustainable systems.</p>
</article>
<article class="book-card">
<h3>A Walk in Mentoring</h3>
<p class="book-status">Published</p>
<p>Three questions that change how people learn.</p>
</article>
<article class="book-card">
<h3>Dark Provenance</h3>
<p class="book-status">Coming 2026</p>
<p>Formal verification for the AI age. How to audit what your AI actually did.</p>
</article>
</div>
</div>
</section>
</BaseLayout>
<style>
.hero {
background: var(--color-cream);
padding: 4rem 2rem;
text-align: center;
}
.hero h1 {
font-family: var(--font-heading);
font-size: clamp(2rem, 5vw, 3rem);
color: var(--color-brown-dark);
margin: 0;
}
.section-container {
max-width: 1000px;
margin: 0 auto;
padding: 4rem 2rem;
}
.bio-section {
background: white;
}
.bio-section .section-container {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 3rem;
align-items: start;
}
@media (max-width: 768px) {
.bio-section .section-container {
grid-template-columns: 1fr;
}
}
.bio-lead {
font-size: 1.125rem;
line-height: 1.7;
color: var(--color-brown);
margin: 0 0 2rem;
}
.bio-lead em {
font-style: normal;
font-weight: 600;
}
.bio-content h2 {
font-family: var(--font-heading);
font-size: 1.5rem;
color: var(--color-brown-dark);
margin: 2rem 0 1rem;
}
.framework-grid {
display: grid;
gap: 1.5rem;
}
.framework-item {
padding: 1.5rem;
background: var(--color-cream);
border-radius: 8px;
}
.framework-item h3 {
font-family: var(--font-heading);
font-size: 1.125rem;
margin: 0 0 0.5rem;
color: var(--color-brown-dark);
}
.framework-item p {
margin: 0;
color: var(--color-text-muted);
}
.photo-placeholder {
background: var(--color-cream-warm);
border-radius: 8px;
padding: 4rem 2rem;
text-align: center;
color: var(--color-text-muted);
}
.photo-placeholder p {
font-size: 1.25rem;
margin: 0;
}
.books-section {
background: var(--color-cream);
}
.books-section h2 {
font-family: var(--font-heading);
font-size: 2rem;
text-align: center;
margin: 0 0 2rem;
color: var(--color-brown-dark);
}
.books-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
}
.book-card {
background: white;
padding: 2rem;
border-radius: 8px;
border: 1px solid var(--color-border);
}
.book-card h3 {
font-family: var(--font-heading);
font-size: 1.25rem;
margin: 0 0 0.5rem;
color: var(--color-brown-dark);
}
.book-status {
font-size: 0.875rem;
font-weight: 600;
color: var(--color-green);
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0 0 0.75rem;
}
.book-card p:last-child {
margin: 0;
color: var(--color-text-muted);
line-height: 1.6;
}
</style>

148
src/pages/blog.astro Normal file
View file

@ -0,0 +1,148 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout
title="Blog — Scott Scoble"
description="Articles on AI, leadership, cultivation, and the human side of technology."
>
<section class="hero">
<div class="hero-content">
<p class="hero-kicker">Writing</p>
<h1>Blog</h1>
<p class="hero-description">
Articles on AI, leadership, cultivation, and the human side of technology.
</p>
</div>
</section>
<section class="posts">
<div class="posts-container">
<article class="post-card">
<div class="post-meta">
<span class="post-date">Coming soon</span>
<span class="post-tag">AI</span>
</div>
<h2>The Memory Problem Nobody Talks About</h2>
<p class="post-excerpt">
Why your AI's "memory" is actually just replay, not learning — and what to do about it.
</p>
</article>
<article class="post-card">
<div class="post-meta">
<span class="post-date">Coming soon</span>
<span class="post-tag">Leadership</span>
</div>
<h2>Three Questions That Change Everything</h2>
<p class="post-excerpt">
The simple mentoring framework that actually gets used.
</p>
</article>
<article class="post-card">
<div class="post-meta">
<span class="post-date">Coming soon</span>
<span class="post-tag">Teams</span>
</div>
<h2>Strip-Mining vs Cultivation</h2>
<p class="post-excerpt">
The mindset shift that separates thriving teams from burned-out ones.
</p>
</article>
</div>
</section>
</BaseLayout>
<style>
.hero {
padding: 8rem 2rem 4rem;
text-align: center;
background: var(--color-cream);
}
.hero-content {
max-width: 600px;
margin: 0 auto;
}
.hero-kicker {
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--color-green);
margin: 0 0 1rem;
}
.hero h1 {
font-family: var(--font-heading);
font-size: clamp(2.5rem, 5vw, 4rem);
color: var(--color-brown-dark);
margin: 0 0 1rem;
}
.hero-description {
font-size: 1.125rem;
color: var(--color-text-muted);
margin: 0;
}
.posts {
padding: 4rem 2rem 6rem;
background: white;
}
.posts-container {
max-width: 800px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 2rem;
}
.post-card {
padding: 2rem;
border: 1px solid var(--color-border-light);
border-radius: 12px;
transition: all 0.2s ease;
}
.post-card:hover {
border-color: var(--color-amber);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.post-meta {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
font-size: 0.875rem;
}
.post-date {
color: var(--color-text-muted);
}
.post-tag {
color: var(--color-amber);
font-weight: 600;
}
.post-card h2 {
font-family: var(--font-heading);
font-size: 1.5rem;
color: var(--color-brown-dark);
margin: 0 0 0.75rem;
}
.post-excerpt {
color: var(--color-text-muted);
margin: 0;
line-height: 1.6;
}
</style>

180
src/pages/books.astro Normal file
View file

@ -0,0 +1,180 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout
title="Books — Scott Scoble"
description="Published works on cultivation, mentoring, and the human side of AI."
>
<section class="hero">
<div class="hero-content">
<h1>Books</h1>
<p>Published works on cultivation, mentoring, and the human side of AI.</p>
</div>
</section>
<section class="books-section">
<div class="section-container">
<div class="books-grid">
<article class="book-card">
<div class="book-cover">
<span>📚</span>
</div>
<h2>The Soil Beneath the Code</h2>
<p class="book-meta">Published 2023</p>
<p class="book-description">What engineering teams can learn from farmers about sustainable systems.</p>
<p class="book-quote">"A cultivation playbook for technical leaders who are tired of strip-mining their teams."</p>
<div class="book-links">
<a href="#" class="book-link">Amazon</a>
<a href="#" class="book-link">Publisher</a>
</div>
</article>
<article class="book-card">
<div class="book-cover">
<span>🚶</span>
</div>
<h2>A Walk in Mentoring</h2>
<p class="book-meta">Published 2024</p>
<p class="book-description">Three questions that change how people learn.</p>
<p class="book-quote">"The simplest mentoring framework you'll actually use."</p>
<div class="book-links">
<a href="#" class="book-link">Amazon</a>
<a href="#" class="book-link">Publisher</a>
</div>
</article>
<article class="book-card coming-soon">
<div class="book-cover">
<span>🔍</span>
</div>
<h2>Dark Provenance</h2>
<p class="book-meta">Coming 2026</p>
<p class="book-description">Formal verification for the AI age. How to audit what your AI actually did.</p>
<p class="book-quote">"Speckl and the dark provenance system. A new approach to AI accountability."</p>
<a href="#" class="waitlist-btn">Join Waitlist</a>
</article>
</div>
</div>
</section>
</BaseLayout>
<style>
.hero {
background: var(--color-cream);
padding: 4rem 2rem;
text-align: center;
}
.hero h1 {
font-family: var(--font-heading);
font-size: clamp(2rem, 5vw, 3rem);
color: var(--color-brown-dark);
margin: 0 0 1rem;
}
.hero p {
font-size: 1.125rem;
color: var(--color-text-muted);
margin: 0;
}
.books-section {
background: white;
}
.section-container {
max-width: 1100px;
margin: 0 auto;
padding: 4rem 2rem;
}
.books-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2.5rem;
}
.book-card {
background: var(--color-cream);
padding: 2.5rem;
border-radius: 8px;
text-align: center;
}
.book-card.coming-soon {
background: var(--color-cream-warm);
border: 2px dashed var(--color-border);
}
.book-cover {
font-size: 4rem;
margin-bottom: 1.5rem;
}
.book-card h2 {
font-family: var(--font-heading);
font-size: 1.5rem;
margin: 0 0 0.5rem;
color: var(--color-brown-dark);
}
.book-meta {
font-size: 0.875rem;
font-weight: 600;
color: var(--color-green);
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0 0 1rem;
}
.book-description {
color: var(--color-brown);
margin: 0 0 1rem;
line-height: 1.6;
}
.book-quote {
font-style: italic;
color: var(--color-text-muted);
margin: 0 0 1.5rem;
}
.book-links {
display: flex;
gap: 1rem;
justify-content: center;
}
.book-link {
color: var(--color-green);
text-decoration: none;
font-weight: 500;
}
.book-link:hover {
text-decoration: underline;
}
.waitlist-btn {
display: inline-block;
background: var(--color-brown);
color: white;
padding: 0.75rem 1.5rem;
border-radius: 4px;
text-decoration: none;
font-weight: 500;
}
.waitlist-btn:hover {
background: var(--color-brown-dark);
}
</style>

229
src/pages/consulting.astro Normal file
View file

@ -0,0 +1,229 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import PackageCard from '../components/PackageCard.astro';
---
<BaseLayout
title="Consulting — Scott Scoble"
description="Consulting packages for engineering leaders: Soil Assessment, Cultivation Workshop, and Fractional CTO services."
>
<section class="hero">
<div class="hero-content">
<h1>Consulting</h1>
<p>Diagnose, cultivate, and build systems that make humans and AI work together.</p>
</div>
</section>
<section class="packages-section">
<div class="section-container">
<h2>Three Ways to Engage</h2>
<div class="packages-grid">
<PackageCard
title="Soil Assessment"
tagline="DIAGNOSTIC"
description="Diagnose your team's soil health. Find what's actually depleted, not what's loudest."
deliverables={[
"2-hour diagnostic interview with leadership team",
"Soil health report: what to cultivate, what to prune",
"90-day cultivation roadmap",
"Follow-up recommendations"
]}
priceRange="Starting at $5,000"
idealFor="Teams adopting AI but losing human coordination"
/>
<PackageCard
title="Cultivation Workshop"
tagline="INTERVENTION"
description="Turn retrospectives into structural insights, not blame sessions."
deliverables={[
"Full-day facilitated workshop with your team",
"Three-question mentoring framework training",
"Accountability mandate and cultivation practices",
"Follow-up coaching session"
]}
priceRange="Starting at $15,000"
idealFor="Teams stuck in reactive mode, need structural change"
/>
<PackageCard
title="Fractional CTO"
tagline="ONGOING PARTNERSHIP"
description="Ongoing advisory for agentic systems. Hold the line on human infrastructure."
deliverables={[
"Weekly advisory calls",
"Code review and architecture guidance",
"Speckl dark provenance audit",
"Team mentoring and development"
]}
priceRange="$8,000/month"
idealFor="Startups and mid-size companies building agentic systems"
/>
</div>
</div>
</section>
<section class="faq-section">
<div class="section-container">
<h2>Frequently Asked Questions</h2>
<dl class="faq-list">
<div class="faq-item">
<dt>What's the difference between the packages?</dt>
<dd>Assessment is diagnostic — we figure out what's going on. Workshop is intervention — we change how the team operates. Fractional is ongoing partnership — I embed with your team over time.</dd>
</div>
<div class="faq-item">
<dt>How do we know which one we need?</dt>
<dd>Start with a call. I don't sell packages — I solve problems. We'll figure out the right engagement based on your actual situation.</dd>
</div>
<div class="faq-item">
<dt>Do you work with remote teams?</dt>
<dd>Yes. Most of my work is remote-first. I have async practices that work across time zones.</dd>
</div>
<div class="faq-item">
<dt>What's your typical engagement timeline?</dt>
<dd>Assessment: 2 weeks. Workshop: 1 week planning + 1 day delivery + 2 weeks follow-up. Fractional: 3–6 month minimum.</dd>
</div>
</dl>
</div>
</section>
<section class="cta-section">
<div class="section-container">
<h2>Ready to cultivate your team?</h2>
<p>Schedule a discovery call. We'll talk about your situation and figure out the right engagement.</p>
<a href="/contact" class="cta-button">Schedule Discovery Call</a>
</div>
</section>
</BaseLayout>
<style>
.hero {
background: var(--color-cream);
padding: 4rem 2rem;
text-align: center;
}
.hero h1 {
font-family: var(--font-heading);
font-size: clamp(2rem, 5vw, 3rem);
color: var(--color-brown-dark);
margin: 0 0 1rem;
}
.hero p {
font-size: 1.25rem;
color: var(--color-text-muted);
margin: 0;
max-width: 600px;
margin: 0 auto;
}
.section-container {
max-width: 1000px;
margin: 0 auto;
padding: 4rem 2rem;
}
.packages-section {
background: white;
}
.packages-section h2 {
font-family: var(--font-heading);
font-size: 2rem;
text-align: center;
margin: 0 0 3rem;
color: var(--color-brown-dark);
}
.packages-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
align-items: start;
}
.faq-section {
background: var(--color-cream-warm);
}
.faq-section h2 {
font-family: var(--font-heading);
font-size: 2rem;
text-align: center;
margin: 0 0 3rem;
color: var(--color-brown-dark);
}
.faq-list {
max-width: 700px;
margin: 0 auto;
}
.faq-item {
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid var(--color-border);
}
.faq-item:last-child {
border-bottom: none;
}
.faq-item dt {
font-weight: 600;
font-size: 1.125rem;
color: var(--color-brown-dark);
margin: 0 0 0.5rem;
}
.faq-item dd {
margin: 0;
color: var(--color-text-muted);
line-height: 1.6;
}
.cta-section {
background: var(--color-brown-dark);
color: white;
text-align: center;
}
.cta-section h2 {
font-family: var(--font-heading);
font-size: 2rem;
margin: 0 0 1rem;
}
.cta-section p {
color: rgba(255,255,255,0.8);
margin: 0 0 2rem;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.cta-button {
display: inline-block;
background: var(--color-amber);
color: white;
padding: 0.875rem 2rem;
border-radius: 4px;
text-decoration: none;
font-weight: 600;
font-size: 1.0625rem;
}
.cta-button:hover {
background: var(--color-amber-light);
}
</style>

204
src/pages/contact.astro Normal file
View file

@ -0,0 +1,204 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout
title="Contact — Scott Scoble"
description="Get in touch about speaking, consulting, or just to say hello."
>
<section class="hero">
<div class="hero-content">
<h1>Contact</h1>
<p>Let's talk about your event, your team, or your AI systems.</p>
</div>
</section>
<section class="contact-section">
<div class="section-container">
<div class="contact-grid">
<!-- Contact Form -->
<div class="contact-form-wrapper">
<h2>Send a Message</h2>
<form action="https://formspree.io/f/PLACEHOLDER" method="POST">
<div class="form-group">
<label for="name">Name *</label>
<input type="text" id="name" name="name" required />
</div>
<div class="form-group">
<label for="email">Email *</label>
<input type="email" id="email" name="email" required />
</div>
<div class="form-group">
<label for="topic">Topic</label>
<select id="topic" name="topic">
<option value="">Select...</option>
<option value="speaking">Speaking Inquiry</option>
<option value="consulting">Consulting Inquiry</option>
<option value="general">General</option>
</select>
</div>
<div class="form-group">
<label for="message">Message *</label>
<textarea id="message" name="message" rows="5" required></textarea>
</div>
<button type="submit" class="submit-btn">Send Message</button>
</form>
</div>
<!-- Alternative Contact -->
<div class="contact-alternatives">
<h2>Other Ways to Reach Me</h2>
<div class="contact-option">
<h3>📧 Email</h3>
<p><a href="mailto:scott@scoble.me">scott@scoble.me</a></p>
</div>
<div class="contact-option">
<h3>📅 Discovery Call</h3>
<p><a href="#">Schedule a 20-min call</a> (Calendly link coming soon)</p>
</div>
<div class="contact-option">
<h3>🐦 Social</h3>
<p>
<a href="https://linkedin.com/in/scottscoble" target="_blank" rel="noopener">LinkedIn</a> •
<a href="https://twitter.com/scottscoble" target="_blank" rel="noopener">X/Twitter</a> •
<a href="https://github.com/sscoble" target="_blank" rel="noopener">GitHub</a>
</p>
</div>
</div>
</div>
</div>
</section>
</BaseLayout>
<style>
.hero {
background: var(--color-cream);
padding: 4rem 2rem;
text-align: center;
}
.hero h1 {
font-family: var(--font-heading);
font-size: clamp(2rem, 5vw, 3rem);
color: var(--color-brown-dark);
margin: 0 0 1rem;
}
.hero p {
font-size: 1.125rem;
color: var(--color-text-muted);
margin: 0;
}
.section-container {
max-width: 1000px;
margin: 0 auto;
padding: 4rem 2rem;
}
.contact-section {
background: white;
}
.contact-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
}
@media (max-width: 768px) {
.contact-grid {
grid-template-columns: 1fr;
gap: 3rem;
}
}
.contact-form-wrapper h2,
.contact-alternatives h2 {
font-family: var(--font-heading);
font-size: 1.5rem;
margin: 0 0 1.5rem;
color: var(--color-brown-dark);
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
font-weight: 500;
margin-bottom: 0.375rem;
color: var(--color-brown-dark);
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 0.625rem;
border: 1px solid var(--color-border);
border-radius: 4px;
font-size: 1rem;
font-family: inherit;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--color-green);
}
.submit-btn {
background: var(--color-amber);
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
}
.submit-btn:hover {
background: var(--color-amber-light);
}
.contact-option {
margin-bottom: 2rem;
}
.contact-option h3 {
font-family: var(--font-heading);
font-size: 1.125rem;
margin: 0 0 0.5rem;
color: var(--color-brown-dark);
}
.contact-option p {
margin: 0;
color: var(--color-text-muted);
}
.contact-option a {
color: var(--color-green);
text-decoration: none;
}
.contact-option a:hover {
text-decoration: underline;
}
</style>

572
src/pages/index.astro Normal file
View file

@ -0,0 +1,572 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import PillarCard from '../components/PillarCard.astro';
---
<BaseLayout
title="Scott Scoble — Human Cultivation Expert for the AI Age"
description="Keynote speaker and consultant helping leaders build AI systems that cultivate humans instead of strip-mining them. Author of The Soil Beneath the Code."
>
<!-- Hero Section -->
<section class="hero">
<div class="hero-bg">
<img src="/images/hero-speaker.svg" alt="" class="hero-image" />
<div class="hero-overlay"></div>
</div>
<div class="hero-content">
<p class="hero-kicker">Keynote Speaker · Consultant · Author</p>
<h1>
<span class="line">The human</span>
<span class="line accent">cultivation</span>
<span class="line">expert for the AI age.</span>
</h1>
<p class="hero-description">
Keynotes and workshops for leaders building AI systems that don't strip-mine their humans.
</p>
<div class="hero-actions">
<a href="/speaking#book" class="btn btn-primary">Book Scott for Your Event</a>
<a href="/speaking" class="btn btn-outline">Watch the Reel</a>
</div>
</div>
</section>
<!-- Three Pillars -->
<section class="pillars">
<div class="section-header">
<p class="section-kicker">Three Pillars</p>
<h2>How I Help Teams Thrive</h2>
</div>
<div class="pillar-grid">
<PillarCard
number={1}
title="Cultivation Over Strip Mining"
description="Build AI systems that cultivate human capability, not extract it. Assessment, diagnosis, and cultivation practices for engineering teams."
/>
<PillarCard
number={2}
title="Simple Mentoring"
description="Three questions that change how people learn. The mentoring framework that actually gets used. Workshop delivery and team training."
/>
<PillarCard
number={3}
title="Human Side of AI"
description="Maximize the people dimension in AI-augmented work. Keynotes and workshops on the human infrastructure for agentic systems."
/>
</div>
</section>
<!-- Featured Talks -->
<section class="featured-talks">
<div class="section-header">
<p class="section-kicker">Signature Talks</p>
<h2>What I Speak About</h2>
</div>
<div class="talks-list">
<article class="talk-card">
<div class="talk-meta">
<span class="talk-type">Keynote</span>
<span class="talk-duration">45–60 min</span>
</div>
<h3>50 First Dates with AI</h3>
<p class="talk-hook">Every morning, your AI wakes up and doesn't remember yesterday. Here's how to fix that.</p>
<p class="talk-desc">Current AI memory architecture is anterograde amnesia — and we're all Henry Roth, recording VHS tapes every night so our agent can wake up functional tomorrow.</p>
<a href="/speaking" class="talk-link">Learn more →</a>
</article>
<article class="talk-card featured">
<div class="talk-meta">
<span class="talk-type">Keynote</span>
<span class="talk-duration">45–60 min</span>
</div>
<h3>From Strip-Mining to Cultivation</h3>
<p class="talk-hook">The shift from extraction to cultivation in AI-era teams. Why throughput optimization is killing your soil health.</p>
<p class="talk-desc">The most successful AI teams aren't the ones with the best models. They're the ones who stopped strip-mining their humans and started cultivating them.</p>
<a href="/speaking" class="talk-link">Learn more →</a>
</article>
<article class="talk-card">
<div class="talk-meta">
<span class="talk-type">Workshop</span>
<span class="talk-duration">Half-day</span>
</div>
<h3>The Soil Under Your Team</h3>
<p class="talk-hook">Diagnose what's actually depleted before you fertilize. A diagnostic framework for team health.</p>
<p class="talk-desc">Most team interventions fail because they prescribe before diagnosing. This workshop gives you a cultivation assessment framework that finds what's actually depleted.</p>
<a href="/speaking" class="talk-link">Learn more →</a>
</article>
</div>
</section>
<!-- Trust Bar -->
<section class="trust-bar">
<p class="trust-label">Trusted by leaders at</p>
<div class="trust-logos">
<span class="logo-placeholder">[Client logos coming soon]</span>
</div>
</section>
<!-- Testimonials -->
<section class="testimonials">
<div class="section-header">
<p class="section-kicker">Testimonials</p>
<h2>What People Say</h2>
</div>
<div class="testimonial-grid">
<blockquote class="testimonial-card">
<p class="testimonial-quote">"Scott has a rare ability to diagnose team dynamics quickly and prescribe actionable solutions. His cultivation framework changed how we think about AI adoption."</p>
<footer class="testimonial-author">
<strong>Engineering Leader</strong>
<span>Tech Company</span>
</footer>
</blockquote>
</div>
</section>
<!-- CTA Section -->
<section class="cta-section">
<div class="cta-visual"></div>
<div class="cta-content">
<p class="cta-kicker">Ready to get started?</p>
<h2>Let's cultivate your team.</h2>
<p class="cta-desc">Book a keynote, workshop, or discovery call. Let's talk about your event or your team's soil health.</p>
<a href="/speaking#book" class="btn btn-primary btn-large">Book Scott</a>
</div>
</section>
</BaseLayout>
<style>
/* ===== HERO ===== */
.hero {
position: relative;
min-height: 90vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
padding: 6rem 2rem;
background: var(--color-cream);
overflow: hidden;
}
.hero-bg {
position: absolute;
inset: 0;
z-index: 0;
}
.hero-image {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center 30%;
}
.hero-overlay {
position: absolute;
inset: 0;
background: linear-gradient(
180deg,
rgba(250, 248, 245, 0.7) 0%,
rgba(250, 248, 245, 0.5) 40%,
rgba(250, 248, 245, 0.8) 100%
);
}
.hero-content {
position: relative;
max-width: 800px;
z-index: 1;
}
.hero-kicker {
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--color-green);
margin: 0 0 1.5rem;
}
.hero h1 {
font-family: var(--font-heading);
font-size: clamp(2.5rem, 6vw, 5rem);
font-weight: 600;
line-height: 1.1;
color: var(--color-brown-dark);
margin: 0 0 1.5rem;
}
.hero h1 .line {
display: block;
}
.hero h1 .accent {
color: var(--color-amber);
font-style: italic;
}
.hero-description {
font-size: clamp(1.125rem, 2vw, 1.375rem);
color: var(--color-text-muted);
margin: 0 0 2.5rem;
line-height: 1.7;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.hero-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
/* ===== BUTTONS ===== */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.875rem 2rem;
font-size: 1rem;
font-weight: 600;
text-decoration: none;
border-radius: 6px;
transition: all 0.2s ease;
cursor: pointer;
border: 2px solid transparent;
}
.btn-primary {
background: var(--color-amber);
color: white;
border-color: var(--color-amber);
}
.btn-primary:hover {
background: var(--color-amber-light);
border-color: var(--color-amber-light);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(200, 149, 46, 0.3);
}
.btn-outline {
background: transparent;
color: var(--color-brown-dark);
border-color: var(--color-brown-dark);
}
.btn-outline:hover {
background: var(--color-brown-dark);
color: white;
}
.btn-large {
padding: 1rem 2.5rem;
font-size: 1.125rem;
}
/* ===== SECTIONS ===== */
.section-header {
text-align: center;
margin-bottom: 4rem;
padding: 0 2rem;
}
.section-kicker {
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.15em;
color: var(--color-amber);
margin: 0 0 0.75rem;
}
.section-header h2 {
font-family: var(--font-heading);
font-size: clamp(2rem, 4vw, 3rem);
color: var(--color-brown-dark);
margin: 0;
}
/* ===== PILLARS ===== */
.pillars {
padding: 6rem 2rem;
background: white;
}
.pillar-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 2rem;
max-width: 1200px;
margin: 0 auto;
}
/* ===== TALKS ===== */
.featured-talks {
padding: 6rem 2rem;
background: linear-gradient(180deg, #f5f0e8 0%, #faf8f5 100%);
}
.talks-list {
display: flex;
flex-direction: column;
gap: 2rem;
max-width: 900px;
margin: 0 auto;
}
.talk-card {
background: white;
padding: 2.5rem;
border-radius: 12px;
border: 1px solid var(--color-border-light);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
transition: all 0.3s ease;
}
.talk-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
}
.talk-card.featured {
border-left: 4px solid var(--color-amber);
}
.talk-meta {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}
.talk-type,
.talk-duration {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.1em;
padding: 0.25rem 0.75rem;
border-radius: 20px;
background: var(--color-cream-warm);
color: var(--color-brown);
}
.talk-card h3 {
font-family: var(--font-heading);
font-size: 1.75rem;
color: var(--color-brown-dark);
margin: 0 0 0.75rem;
}
.talk-hook {
font-size: 1.125rem;
font-weight: 500;
color: var(--color-green);
margin: 0 0 1rem;
line-height: 1.5;
}
.talk-desc {
color: var(--color-text-muted);
margin: 0 0 1.5rem;
line-height: 1.7;
}
.talk-link {
color: var(--color-amber);
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.talk-link:hover {
color: var(--color-brown-dark);
}
/* ===== TRUST BAR ===== */
.trust-bar {
padding: 3rem 2rem;
background: var(--color-brown-dark);
text-align: center;
}
.trust-label {
font-size: 0.875rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.1em;
color: rgba(255, 255, 255, 0.6);
margin: 0 0 1.5rem;
}
.trust-logos {
display: flex;
justify-content: center;
gap: 3rem;
flex-wrap: wrap;
}
.logo-placeholder {
color: rgba(255, 255, 255, 0.4);
font-style: italic;
}
/* ===== TESTIMONIALS ===== */
.testimonials {
padding: 6rem 2rem;
background: white;
}
.testimonial-grid {
max-width: 800px;
margin: 0 auto;
}
.testimonial-card {
background: linear-gradient(135deg, #faf8f5 0%, #f5f0e8 100%);
padding: 3rem;
border-radius: 16px;
border: none;
position: relative;
}
.testimonial-card::before {
content: '"';
position: absolute;
top: 1rem;
left: 1.5rem;
font-family: var(--font-heading);
font-size: 6rem;
color: var(--color-amber);
opacity: 0.3;
line-height: 1;
}
.testimonial-quote {
font-size: 1.25rem;
font-style: italic;
color: var(--color-brown-dark);
margin: 0 0 1.5rem;
line-height: 1.7;
position: relative;
z-index: 1;
}
.testimonial-author {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.testimonial-author strong {
color: var(--color-brown-dark);
font-weight: 600;
}
.testimonial-author span {
color: var(--color-text-muted);
font-size: 0.9375rem;
}
/* ===== CTA SECTION ===== */
.cta-section {
position: relative;
padding: 6rem 2rem;
background: var(--color-green);
text-align: center;
overflow: hidden;
}
.cta-visual {
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(61, 90, 62, 0.95) 0%, rgba(45, 90, 62, 0.98) 100%);
}
.cta-visual::before {
content: '';
position: absolute;
top: -50%;
right: -20%;
width: 600px;
height: 600px;
background: radial-gradient(circle, rgba(200, 149, 46, 0.2) 0%, transparent 60%);
border-radius: 50%;
}
.cta-content {
position: relative;
z-index: 1;
max-width: 600px;
margin: 0 auto;
}
.cta-kicker {
font-size: 0.875rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.15em;
color: rgba(255, 255, 255, 0.7);
margin: 0 0 1rem;
}
.cta-content h2 {
font-family: var(--font-heading);
font-size: clamp(2rem, 4vw, 3rem);
color: white;
margin: 0 0 1rem;
}
.cta-desc {
font-size: 1.125rem;
color: rgba(255, 255, 255, 0.85);
margin: 0 0 2rem;
line-height: 1.7;
}
.cta-section .btn-primary {
background: var(--color-amber);
border-color: var(--color-amber);
}
.cta-section .btn-primary:hover {
background: var(--color-amber-light);
border-color: var(--color-amber-light);
}
@media (max-width: 640px) {
.hero {
min-height: auto;
padding: 4rem 1.5rem;
}
.section-header {
margin-bottom: 2.5rem;
}
.pillars,
.featured-talks,
.testimonials,
.cta-section {
padding: 4rem 1.5rem;
}
.talk-card {
padding: 1.5rem;
}
.testimonial-card {
padding: 2rem 1.5rem;
}
}
</style>

82
src/pages/privacy.astro Normal file
View file

@ -0,0 +1,82 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout
title="Privacy Policy — Scott Scoble"
description="Privacy policy for scott.scoble.me"
>
<section class="page-header">
<div class="container">
<h1>Privacy Policy</h1>
</div>
</section>
<section class="content">
<div class="container">
<p>Last updated: April 28, 2026</p>
<h2>Overview</h2>
<p>This website collects minimal data. Here's what you should know:</p>
<h2>Contact Forms</h2>
<p>If you submit a contact form, your email and message are sent to me directly. I don't share this information with third parties.</p>
<h2>Analytics</h2>
<p>This site does not use Google Analytics or other tracking scripts.</p>
<h2>Cookies</h2>
<p>No cookies are set by this site.</p>
<h2>Hosting</h2>
<p>This site is hosted on Storj. Their privacy policy applies to infrastructure-level logging.</p>
<h2>Contact</h2>
<p>Questions? Email <a href="mailto:scott@scoble.me">scott@scoble.me</a>.</p>
</div>
</section>
</BaseLayout>
<style>
.page-header {
padding: 8rem 2rem 2rem;
text-align: center;
background: var(--color-cream);
}
.page-header h1 {
font-family: var(--font-heading);
font-size: clamp(2rem, 5vw, 3rem);
color: var(--color-brown-dark);
margin: 0;
}
.content {
padding: 4rem 2rem 6rem;
background: white;
}
.container {
max-width: 700px;
margin: 0 auto;
}
.content h2 {
font-family: var(--font-heading);
font-size: 1.5rem;
color: var(--color-brown-dark);
margin: 2rem 0 1rem;
}
.content p {
color: var(--color-text-muted);
line-height: 1.7;
margin: 0 0 1rem;
}
.content a {
color: var(--color-green);
}
</style>

392
src/pages/speaking.astro Normal file
View file

@ -0,0 +1,392 @@
---
import BaseLayout from '../layouts/BaseLayout.astro';
import TalkCard from '../components/TalkCard.astro';
---
<BaseLayout
title="Speaking — Scott Scoble"
description="Keynote speaker on AI, leadership, and human cultivation. Book Scott for your next conference or corporate event."
>
<!-- Hero -->
<section class="hero">
<div class="hero-content">
<p class="hero-kicker">Keynote Speaker</p>
<h1>Helping leaders build AI systems that cultivate humans instead of strip-mining them.</h1>
<a href="#book" class="hero-cta">Book Scott for Your Event</a>
</div>
</section>
<!-- Speaker Reel -->
<section class="reel-section">
<div class="section-container">
<h2>Watch the Reel</h2>
<div class="video-container">
<div class="video-placeholder">
<p>🎬 Speaker reel in production</p>
<p>Coming soon — 3-minute highlight reel from keynotes and workshops.</p>
</div>
</div>
</div>
</section>
<!-- Signature Talks -->
<section class="talks-section">
<div class="section-container">
<h2>Signature Talks</h2>
<div class="talks-list">
<TalkCard
title="50 First Dates with AI"
premise="Current AI memory architecture is anterograde amnesia — and we're all Henry Roth, recording VHS tapes every night so our agent can wake up functional tomorrow."
idealFor="Engineering leaders, AI product teams, CTOs implementing agentic systems"
takeaways={[
"Why your AI's 'memory' is actually just replay, not learning",
"The three-layer memory architecture that actually compounds",
"How to move from VHS tapes to persistent, evolving agents"
]}
/>
<TalkCard
title="From Strip-Mining to Cultivation"
premise="The shift from extraction to cultivation in AI-era teams. Why throughput optimization is killing your soil health."
idealFor="CTOs, VP Engineering, people leaders, engineering managers"
takeaways={[
"The difference between strip-mining and cultivation mindsets",
"How to assess your team's soil health",
"Practices that cultivate instead of extract"
]}
/>
<TalkCard
title="The Soil Under Your Team"
premise="Diagnose what's actually depleted before you fertilize. A diagnostic framework for team health in AI-augmented work."
idealFor="Team leads, managers, engineering directors, founders"
takeaways={[
"The four soil nutrients every team needs",
"How to run a cultivation assessment",
"From diagnosis to cultivation roadmap"
]}
/>
</div>
</div>
</section>
<!-- Past Events -->
<section class="events-section">
<div class="section-container">
<h2>Past Events</h2>
<div class="events-list">
<article class="event-item">
<h3>Game Changers Summit 2026</h3>
<p>Featured Speaker — "From Strip-Mining to Cultivation"</p>
</article>
<article class="event-item">
<h3>AI Engineering Leadership Forum</h3>
<p>Keynote — "50 First Dates with AI"</p>
</article>
</div>
</div>
</section>
<!-- Book Scott Section -->
<section id="book" class="book-section">
<div class="section-container">
<h2>Book Scott</h2>
<div class="book-grid">
<!-- Booking Form -->
<div class="book-form">
<h3>Inquiry Form</h3>
<form action="https://formspree.io/f/PLACEHOLDER" method="POST">
<div class="form-group">
<label for="name">Name *</label>
<input type="text" id="name" name="name" required />
</div>
<div class="form-group">
<label for="email">Email *</label>
<input type="email" id="email" name="email" required />
</div>
<div class="form-group">
<label for="event-date">Event Date</label>
<input type="date" id="event-date" name="event-date" />
</div>
<div class="form-group">
<label for="event-type">Event Type</label>
<select id="event-type" name="event-type">
<option value="">Select...</option>
<option value="conference">Conference</option>
<option value="corporate">Corporate Event</option>
<option value="workshop">Workshop</option>
<option value="other">Other</option>
</select>
</div>
<div class="form-group">
<label for="message">Message *</label>
<textarea id="message" name="message" rows="4" required></textarea>
</div>
<button type="submit" class="submit-btn">Send Inquiry</button>
</form>
</div>
<!-- One Sheet & Quick Info -->
<div class="book-info">
<h3>Quick Info</h3>
<ul>
<li><strong>Keynote length:</strong> 45–60 minutes</li>
<li><strong>Workshop length:</strong> Half-day or full-day</li>
<li><strong>Topics:</strong> AI, leadership, team cultivation, mentoring</li>
<li><strong>Audience:</strong> Engineering leaders, CTOs, people managers</li>
</ul>
<a href="/downloads/Scott-Scoble-Human-Cultivation-Expert-Speaker-Profile.pdf" class="download-btn" download>📄 Download Speaker Profile (PDF)</a>
<p class="contact-alt">Or email: <a href="mailto:scott@scoble.me">scott@scoble.me</a></p>
</div>
</div>
</div>
</section>
</BaseLayout>
<style>
.hero {
background: var(--color-cream);
padding: 5rem 2rem 4rem;
text-align: center;
}
.hero-content {
max-width: 800px;
margin: 0 auto;
}
.hero-kicker {
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.1em;
color: var(--color-green);
font-weight: 600;
margin: 0 0 1rem;
}
.hero h1 {
font-family: var(--font-heading);
font-size: clamp(1.75rem, 4vw, 2.5rem);
color: var(--color-brown-dark);
margin: 0 0 2rem;
line-height: 1.3;
}
.hero-cta {
display: inline-block;
background: var(--color-amber);
color: white;
padding: 0.875rem 2rem;
border-radius: 4px;
text-decoration: none;
font-weight: 600;
}
.section-container {
max-width: 1000px;
margin: 0 auto;
padding: 4rem 2rem;
}
section h2 {
font-family: var(--font-heading);
font-size: 2rem;
margin: 0 0 2rem;
color: var(--color-brown-dark);
}
.reel-section {
background: white;
}
.video-container {
aspect-ratio: 16 / 9;
background: var(--color-cream-warm);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.video-placeholder {
text-align: center;
color: var(--color-text-muted);
}
.video-placeholder p:first-child {
font-size: 1.5rem;
margin: 0 0 0.5rem;
}
.talks-section {
background: var(--color-cream);
}
.talks-list {
display: flex;
flex-direction: column;
gap: 2rem;
}
.events-section {
background: white;
}
.events-list {
display: grid;
gap: 1.5rem;
}
.event-item {
padding: 1.5rem;
background: var(--color-cream);
border-radius: 8px;
}
.event-item h3 {
font-family: var(--font-heading);
margin: 0 0 0.5rem;
color: var(--color-brown-dark);
}
.event-item p {
margin: 0;
color: var(--color-text-muted);
}
.book-section {
background: var(--color-brown-dark);
color: white;
}
.book-section h2 {
color: white;
text-align: center;
}
.book-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
}
@media (max-width: 768px) {
.book-grid {
grid-template-columns: 1fr;
}
}
.book-form {
background: white;
color: var(--color-text);
padding: 2rem;
border-radius: 8px;
}
.book-form h3 {
font-family: var(--font-heading);
margin: 0 0 1.5rem;
color: var(--color-brown-dark);
}
.form-group {
margin-bottom: 1.25rem;
}
.form-group label {
display: block;
font-weight: 500;
margin-bottom: 0.375rem;
color: var(--color-brown-dark);
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 0.625rem;
border: 1px solid var(--color-border);
border-radius: 4px;
font-size: 1rem;
font-family: inherit;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--color-green);
}
.submit-btn {
background: var(--color-amber);
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
width: 100%;
}
.submit-btn:hover {
background: var(--color-amber-light);
}
.book-info h3 {
font-family: var(--font-heading);
margin: 0 0 1.5rem;
}
.book-info ul {
list-style: none;
padding: 0;
margin: 0 0 1.5rem;
}
.book-info li {
margin-bottom: 0.75rem;
padding-left: 0;
}
.download-btn {
display: block;
background: var(--color-amber);
color: white;
padding: 0.75rem 1.25rem;
border-radius: 4px;
text-decoration: none;
font-weight: 500;
margin-bottom: 1rem;
}
.contact-alt {
color: rgba(255,255,255,0.7);
}
.contact-alt a {
color: var(--color-amber-light);
}
</style>

103
src/pages/wp-admin.astro Normal file
View file

@ -0,0 +1,103 @@
---
// Honeypot page — static version, logs via client-side
const honeypotLog = {
timestamp: new Date().toISOString(),
message: 'Honeypot page accessed'
};
---
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WordPress › Login</title>
<meta name="robots" content="noindex,nofollow">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #f0f0f1;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.login-form {
background: #fff;
padding: 40px;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
width: 320px;
}
.login-form h1 {
font-size: 1.5rem;
margin-bottom: 1.5rem;
color: #1d2327;
}
.login-form input {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #8c8f94;
border-radius: 4px;
font-size: 14px;
}
.login-form button {
width: 100%;
padding: 10px;
background: #2271b1;
color: #fff;
border: none;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
.login-form button:hover {
background: #135e96;
}
.login-form .message {
margin-top: 15px;
padding: 10px;
background: #fcf3e8;
border-left: 4px solid #dba617;
font-size: 13px;
color: #3c434a;
}
</style>
</head>
<body>
<div class="login-form">
<h1>WordPress</h1>
<form method="post" action="/wp-admin">
<input type="text" name="log" placeholder="Username or Email Address" autocomplete="off" >
<input type="password" name="pwd" placeholder="Password" >
<button type="submit" id="submit-btn">Log In</button>
</form>
<div class="message" id="message" style="display:none;">
<strong>ERROR:</strong> Incorrect username or password. Please try again.
</div>
</div>
<script>
// Waste their time with fake loading
document.querySelector('form').addEventListener('submit', function(e) {
e.preventDefault();
var btn = document.getElementById('submit-btn');
btn.disabled = true;
btn.textContent = 'Logging in...';
// 3 second fake delay, then error
setTimeout(function() {
btn.disabled = false;
btn.textContent = 'Log In';
document.getElementById('message').style.display = 'block';
// Clear the form so they try again
document.querySelector('[name="log"]').value = '';
document.querySelector('[name="pwd"]').value = '';
document.querySelector('[name="log"]').focus();
}, 3000);
});
</script>
</body>
</html>

479
src/styles/global.css Normal file
View file

@ -0,0 +1,479 @@
/* ==========================================================================
Brand Site — Global Styles
Design System: "Cultivation Aesthetic"
========================================================================== */
/* --- Google Fonts (EB Garamond) --- */
@import url('https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500&display=swap');
/* --- CSS Variables (Design Tokens) --- */
:root {
/* Colors */
--color-brown: #4a4438;
--color-brown-dark: #2d2a24;
--color-green: #3d5a3e;
--color-green-dark: #2d5a3e;
--color-cream: #faf8f5;
--color-cream-warm: #f5f0e8;
--color-amber: #c8952e;
--color-amber-light: #d4a94e;
--color-text: #2d2a24;
--color-text-muted: #6b6458;
--color-border: #e0dbd2;
--color-border-light: #ede9e2;
--color-bg: #faf8f5;
--color-bg-alt: #f5f0e8;
--color-focus: #3d5a3e;
/* Typography */
--font-heading: 'EB Garamond', Georgia, 'Times New Roman', serif;
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
--font-mono: 'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', monospace;
/* Sizes */
--size-xs: 0.75rem; /* 12px */
--size-sm: 0.875rem; /* 14px */
--size-base: 1rem; /* 16px */
--size-md: 1.125rem; /* 18px */
--size-lg: 1.25rem; /* 20px */
--size-xl: 1.5rem; /* 24px */
--size-2xl: 2rem; /* 32px */
--size-3xl: 2.5rem; /* 40px */
--size-4xl: 3rem; /* 48px */
/* Spacing */
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
--space-2xl: 3rem;
--space-3xl: 4rem;
--space-4xl: 6rem;
/* Layout */
--max-width-prose: 44rem; /* ~704px */
--max-width-wide: 68rem; /* ~1088px */
--nav-height: 4rem;
/* Transitions */
--transition-fast: 150ms ease;
--transition-base: 250ms ease;
}
/* --- Reset & Base --- */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
scroll-padding-top: var(--nav-height);
-webkit-text-size-adjust: 100%;
}
body {
font-family: var(--font-body);
font-size: var(--size-base);
line-height: 1.7;
color: var(--color-text);
background-color: var(--color-bg);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* --- Typography --- */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading);
font-weight: 600;
line-height: 1.2;
color: var(--color-brown-dark);
}
h1 { font-size: var(--size-4xl); letter-spacing: -0.02em; }
h2 { font-size: var(--size-2xl); }
h3 { font-size: var(--size-xl); }
h4 { font-size: var(--size-lg); }
p {
margin-bottom: var(--space-md);
}
a {
color: var(--color-green);
text-decoration: none;
transition: color var(--transition-fast);
}
a:hover,
a:focus-visible {
color: var(--color-amber);
text-decoration: underline;
}
strong {
font-weight: 600;
}
ul, ol {
margin-bottom: var(--space-md);
padding-left: var(--space-xl);
}
li {
margin-bottom: var(--space-sm);
}
blockquote {
border-left: 3px solid var(--color-amber);
padding: var(--space-md) var(--space-lg);
margin: var(--space-lg) 0;
background: var(--color-cream-warm);
font-style: italic;
}
hr {
border: none;
border-top: 1px solid var(--color-border);
margin: var(--space-2xl) 0;
}
code {
font-family: var(--font-mono);
font-size: 0.9em;
padding: 0.1em 0.3em;
background: var(--color-cream-warm);
border-radius: 3px;
}
/* --- Skip to Content --- */
.skip-link {
position: absolute;
top: -100%;
left: var(--space-md);
padding: var(--space-sm) var(--space-md);
background: var(--color-green);
color: white;
font-weight: 600;
border-radius: 4px;
z-index: 9999;
}
.skip-link:focus {
top: var(--space-sm);
}
/* --- Focus Styles --- */
:focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 2px;
border-radius: 2px;
}
/* --- Utility Classes --- */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.container {
width: 100%;
max-width: var(--max-width-wide);
margin: 0 auto;
padding: 0 var(--space-lg);
}
.container-narrow {
max-width: var(--max-width-prose);
}
/* --- Buttons --- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.625rem 1.5rem;
font-family: var(--font-body);
font-size: var(--size-sm);
font-weight: 600;
line-height: 1.5;
text-decoration: none;
border: 2px solid transparent;
border-radius: 4px;
cursor: pointer;
transition: all var(--transition-fast);
}
.btn-primary {
background: var(--color-amber);
color: var(--color-brown-dark);
border-color: var(--color-amber);
}
.btn-primary:hover,
.btn-primary:focus-visible {
background: var(--color-amber-light);
border-color: var(--color-amber-light);
color: var(--color-brown-dark);
text-decoration: none;
}
.btn-outline {
background: transparent;
color: var(--color-green);
border-color: var(--color-green);
}
.btn-outline:hover,
.btn-outline:focus-visible {
background: var(--color-green);
color: white;
text-decoration: none;
}
.btn-large {
padding: 0.75rem 2rem;
font-size: var(--size-md);
}
/* --- Page Sections --- */
.section {
padding: var(--space-4xl) 0;
}
.section-header {
text-align: center;
margin-bottom: var(--space-3xl);
}
.section-header h2 {
margin-bottom: var(--space-sm);
}
.section-header .section-subtitle {
color: var(--color-text-muted);
font-size: var(--size-md);
max-width: 36rem;
margin: 0 auto;
}
/* --- Cards (shared) --- */
.card {
background: white;
border: 1px solid var(--color-border-light);
border-radius: 8px;
padding: var(--space-xl);
transition: box-shadow var(--transition-base) var(--transition-base);
}
.card:hover {
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.07);
}
.card-grid {
display: grid;
gap: var(--space-xl);
}
@media (min-width: 640px) {
.card-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
.card-grid { grid-template-columns: repeat(3, 1fr); }
}
/* --- Hero (shared) --- */
.hero {
padding: var(--space-4xl) 0;
text-align: center;
}
.hero h1 {
margin-bottom: var(--space-md);
}
.hero .hero-subtitle {
font-size: var(--size-xl);
color: var(--color-text-muted);
max-width: var(--max-width-prose);
margin: 0 auto var(--space-lg);
}
.hero .hero-actions {
display: flex;
gap: var(--space-md);
justify-content: center;
flex-wrap: wrap;
}
/* --- Forms --- */
.form-group {
margin-bottom: var(--space-md);
}
.form-group label {
display: block;
margin-bottom: var(--space-xs);
font-weight: 600;
font-size: var(--size-sm);
color: var(--color-brown);
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 0.625rem 0.75rem;
font-family: var(--font-body);
font-size: var(--size-base);
line-height: 1.5;
border: 1px solid var(--color-border);
border-radius: 4px;
background: white;
color: var(--color-text);
transition: border-color var(--transition-fast);
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--color-green);
box-shadow: 0 0 0 3px rgba(61, 90, 62, 0.15);
}
.form-group textarea {
min-height: 8rem;
resize: vertical;
}
/* --- Featured / As Seen In --- */
.featured-logos {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-2xl);
flex-wrap: wrap;
padding: var(--space-2xl) 0;
opacity: 0.6;
}
.featured-logo {
font-family: var(--font-heading);
font-size: var(--size-lg);
color: var(--color-text-muted);
font-style: italic;
}
/* --- Testimonials --- */
.testimonials-grid {
display: grid;
gap: var(--space-xl);
}
@media (min-width: 640px) {
.testimonials-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.testimonials-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* --- Page Header (non-hero pages) --- */
.page-header {
padding: var(--space-3xl) 0 var(--space-2xl);
text-align: center;
}
.page-header h1 {
margin-bottom: var(--space-sm);
}
.page-header p {
color: var(--color-text-muted);
font-size: var(--size-md);
max-width: var(--max-width-prose);
margin: 0 auto;
}
/* --- Prose --- */
.prose {
max-width: var(--max-width-prose);
margin: 0 auto;
}
.prose h2 {
margin-top: var(--space-2xl);
margin-bottom: var(--space-md);
}
.prose h3 {
margin-top: var(--space-xl);
margin-bottom: var(--space-sm);
}
.prose p {
margin-bottom: var(--space-md);
}
.prose ul {
margin-bottom: var(--space-md);
}
/* --- Footer --- */
.site-footer {
background: var(--color-brown-dark);
color: var(--color-cream);
padding: var(--space-3xl) 0 var(--space-xl);
}
.site-footer a {
color: var(--color-amber);
}
.site-footer a:hover {
color: var(--color-amber-light);
}
/* --- Responsive --- */
@media (max-width: 639px) {
:root {
--size-4xl: 2.25rem;
--size-2xl: 1.5rem;
--size-xl: 1.25rem;
}
.section {
padding: var(--space-2xl) 0;
}
.hero {
padding: var(--space-2xl) 0;
}
.container {
padding: 0 var(--space-md);
}
}
@media (min-width: 640px) and (max-width: 1023px) {
:root {
--size-4xl: 2.5rem;
}
}

5
tsconfig.json Normal file
View file

@ -0,0 +1,5 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}