commit 5ff70faa4174d5820f12e3139d3669a4f219d42d Author: Will Estes Date: Mon Jul 20 14:26:28 2026 -0400 Initial Hugo skeleton, adapted from michaelferrara-site Fantasy Michigan theme (forest green / lake teal / birch parchment). Reader-magnet content is placeholder pending Cate's story and cover art. diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..559e081 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,16 @@ +name: deploy +on: + push: + branches: [master] +jobs: + deploy: + runs-on: ubuntu-latest + container: + image: hugomods/hugo:exts + volumes: + - /var/www/cateconners.com:/var/www/cateconners.com + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - run: hugo --minify -d /var/www/cateconners.com diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1c3fa73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +public/ +resources/ +.hugo_build.lock diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..fb31987 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +hugo 0.124.1 diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..dbd3c0f --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,492 @@ +/* ========================================================================== + Cate Conners — site stylesheet + Fonts: EB Garamond (headings), Source Serif 4 (body), Inter (UI chrome) + Palette: Fantasy Michigan — birch parchment / forest green / lake teal + ========================================================================== */ + +/* -------------------------------------------------------------------------- + 1. Font loading + Self-hosted is preferable to a Google Fonts CDN call for both + privacy and reliability. Drop the .woff2 files in /assets/fonts/ + and adjust paths below. If you'd rather use Google Fonts directly, + swap this block for @import statements or a in your head + partial instead — either works, just don't do both. + -------------------------------------------------------------------------- */ + +@font-face { + font-family: "EB Garamond"; + src: url("/fonts/EBGaramond-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "EB Garamond"; + src: url("/fonts/EBGaramond-Medium.woff2") format("woff2"); + font-weight: 600; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Source Serif 4"; + src: url("/fonts/SourceSerif4-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Source Serif 4"; + src: url("/fonts/SourceSerif4-Italic.woff2") format("woff2"); + font-weight: 400; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: "Inter"; + src: url("/fonts/Inter-Regular.woff2") format("woff2"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +/* -------------------------------------------------------------------------- + 2. Design tokens + -------------------------------------------------------------------------- */ + +:root { + /* Color — Fantasy Michigan: birch parchment, forest green, lake teal */ + --color-bg: #f4ede1; + --color-bg-raised: #e8ddc8; /* cards, code blocks, pull-quote background */ + --color-ink: #2b2a1f; /* body text */ + --color-ink-heading: #1c2a1f; /* headings, strongest contrast — deep forest */ + --color-accent: #2f5233; /* links, rules, emphasis — forest green */ + --color-accent-hover: #1f3a22; + --color-brass: #3b6e77; /* metadata, dividers, secondary accent — lake teal */ + --color-border: #cbb98f; + + /* Type */ + --font-body: "Source Serif 4", Georgia, "Times New Roman", serif; + --font-heading: "EB Garamond", "Palatino Linotype", Georgia, serif; + --font-ui: "Inter", "IBM Plex Sans", -apple-system, sans-serif; + + --font-size-base: 19px; + --line-height-base: 1.65; + --measure: 70ch; /* max line length for body text */ + + /* Spacing scale */ + --space-1: 0.5rem; + --space-2: 1rem; + --space-3: 1.75rem; + --space-4: 3rem; + --space-5: 5rem; +} + +/* -------------------------------------------------------------------------- + 3. Base + -------------------------------------------------------------------------- */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + font-size: 100%; +} + +body { + background-color: var(--color-bg); + color: var(--color-ink); + font-family: var(--font-body); + font-size: var(--font-size-base); + line-height: var(--line-height-base); + margin: 0; + -webkit-font-smoothing: antialiased; +} + +/* Main content column */ +main { + max-width: var(--measure); + margin: 0 auto; + padding: var(--space-4) var(--space-2); +} + +/* -------------------------------------------------------------------------- + 4. Headings + -------------------------------------------------------------------------- */ + +h1, h2, h3, h4, h5, h6 { + font-family: var(--font-heading); + color: var(--color-ink-heading); + font-weight: 600; + line-height: 1.25; + margin-top: var(--space-4); + margin-bottom: var(--space-2); +} + +h1 { + font-size: 2.6rem; + margin-top: 0; +} + +h2 { + font-size: 2rem; + border-bottom: 1px solid var(--color-border); + padding-bottom: var(--space-1); +} + +h3 { + font-size: 1.5rem; +} + +h4 { + font-size: 1.2rem; + font-style: italic; + font-weight: 400; +} + +/* -------------------------------------------------------------------------- + 5. Body text elements + -------------------------------------------------------------------------- */ + +p { + margin: 0 0 var(--space-2) 0; +} + +a { + color: var(--color-accent); + text-decoration: underline; + text-decoration-thickness: 1px; + text-underline-offset: 0.15em; +} + +a:hover, +a:focus { + color: var(--color-accent-hover); +} + +/* Visible focus state — important for keyboard/screen-reader navigation */ +a:focus-visible, +button:focus-visible, +input:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; +} + +strong { + color: var(--color-ink-heading); +} + +em, i { + font-style: italic; +} + +blockquote { + margin: var(--space-3) 0; + padding: var(--space-2) var(--space-3); + border-left: 3px solid var(--color-accent); + background-color: var(--color-bg-raised); + font-style: italic; +} + +blockquote p:last-child { + margin-bottom: 0; +} + +hr { + border: none; + border-top: 1px solid var(--color-brass); + width: 4rem; + margin: var(--space-4) auto; +} + +/* -------------------------------------------------------------------------- + 6. Lists + -------------------------------------------------------------------------- */ + +ul, ol { + padding-left: 1.4em; + margin: 0 0 var(--space-2) 0; +} + +li { + margin-bottom: 0.4em; +} + +/* -------------------------------------------------------------------------- + 7. Code / pre + Kept legible rather than decorative. + -------------------------------------------------------------------------- */ + +code, pre { + font-family: "Courier New", monospace; + font-size: 0.9em; +} + +code { + background-color: var(--color-bg-raised); + padding: 0.1em 0.35em; + border-radius: 3px; +} + +pre { + background-color: var(--color-bg-raised); + padding: var(--space-2); + overflow-x: auto; + border-radius: 4px; + border: 1px solid var(--color-border); +} + +pre code { + background: none; + padding: 0; +} + +/* -------------------------------------------------------------------------- + 8. UI chrome — nav, metadata, tags + Sans-serif reserved for these elements only. Never used for body prose. + -------------------------------------------------------------------------- */ + +.site-header, +.site-footer, +nav, +.meta, +.tags { + font-family: var(--font-ui); +} + +.site-header { + padding: var(--space-3) var(--space-2); + border-bottom: 1px solid var(--color-border); +} + +.site-title { + font-family: var(--font-heading); + font-size: 1.6rem; + color: var(--color-ink-heading); + text-decoration: none; +} + +nav a { + text-decoration: none; + color: var(--color-ink); + margin-right: var(--space-2); + font-size: 0.95rem; + letter-spacing: 0.02em; +} + +nav a:hover { + color: var(--color-accent); +} + +.meta { + color: var(--color-brass); + font-size: 0.85rem; + letter-spacing: 0.02em; +} + +.site-footer { + border-top: 1px solid var(--color-border); + padding: var(--space-3) var(--space-2); + color: var(--color-brass); + font-size: 0.85rem; + text-align: center; +} + +/* -------------------------------------------------------------------------- + 9. Skip link + Off-screen until focused — the same pattern needed for any keyboard/ + screen-reader user to bypass the header nav straight to
. + -------------------------------------------------------------------------- */ + +.skip-link { + position: absolute; + left: -9999px; + top: 0; + z-index: 100; + background-color: var(--color-accent); + color: var(--color-bg); + font-family: var(--font-ui); + padding: var(--space-1) var(--space-2); + border-radius: 0 0 4px 0; +} + +.skip-link:focus { + left: 0; +} + +/* -------------------------------------------------------------------------- + 10. Buttons, form fields, and the reader-magnet enrollment funnel + (layouts/partials/course-enroll-form.html, layouts/courses/single.html) + -------------------------------------------------------------------------- */ + +.btn { + display: inline-block; + font-family: var(--font-ui); + font-weight: 600; + font-size: 0.95rem; + letter-spacing: 0.02em; + text-decoration: none; + color: var(--color-bg); + background-color: var(--color-accent); + border: none; + border-radius: 4px; + padding: 0.75rem 1.5rem; + cursor: pointer; +} + +.btn:hover { + background-color: var(--color-accent-hover); + color: var(--color-bg); +} + +label { + display: block; + font-family: var(--font-ui); + font-size: 0.85rem; + font-weight: 600; + margin-bottom: 0.35rem; +} + +input[type="email"], +input[type="text"], +textarea { + width: 100%; + font-family: var(--font-ui); + font-size: 1rem; + color: var(--color-ink); + background-color: var(--color-bg-raised); + border: 1px solid var(--color-border); + border-radius: 4px; + padding: 0.65rem 0.75rem; + margin-bottom: var(--space-2); +} + +/* Honeypot: must stay genuinely hidden from sighted users (not just + visually de-emphasized), or a real visitor filling it in silently kills + their own signup — see course-enroll-form.html's `if (form.website.value) + return;` check. */ +.field-honeypot { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + overflow: hidden; +} + +.course-hero { + text-align: center; +} + +.course-cover { + max-width: 14rem; + width: 100%; + height: auto; + border-radius: 4px; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2); + margin: 0 auto var(--space-3); +} + +.course-subhead { + font-style: italic; + color: var(--color-brass); + margin-top: 0; +} + +.course-social-proof { + font-style: italic; + font-size: 0.9rem; + color: var(--color-brass); +} + +.enroll-form { + max-width: 24rem; + margin: var(--space-3) auto; + text-align: left; +} + +.enroll-status { + text-align: center; + font-family: var(--font-ui); + font-size: 0.9rem; +} + +.enroll-download { + text-align: center; + margin-top: var(--space-2); +} + +.course-repeat-cta { + text-align: center; + margin-top: var(--space-5); +} + +/* -------------------------------------------------------------------------- + 11. Post/article-specific + -------------------------------------------------------------------------- */ + +.post-title { + margin-bottom: var(--space-1); +} + +.post-meta { + margin-bottom: var(--space-4); +} + +.post-content h2:first-of-type { + margin-top: var(--space-3); +} + +/* Drop cap for opening paragraph — optional, purely decorative flourish; + delete this rule if it doesn't earn its keep in practice */ +.post-content > p:first-of-type::first-letter { + font-family: var(--font-heading); + font-size: 3.2em; + float: left; + line-height: 0.8; + padding-right: 0.08em; + padding-top: 0.05em; + color: var(--color-accent); +} + +/* -------------------------------------------------------------------------- + 12. Responsive + -------------------------------------------------------------------------- */ + +@media (max-width: 640px) { + :root { + --font-size-base: 17px; + } + + h1 { + font-size: 2.1rem; + } + + h2 { + font-size: 1.6rem; + } + + main { + padding: var(--space-3) var(--space-2); + } +} + +/* -------------------------------------------------------------------------- + 13. Print + -------------------------------------------------------------------------- */ + +@media print { + body { + background: #fff; + color: #000; + } + + nav, .site-footer { + display: none; + } +} diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..7adf8f5 --- /dev/null +++ b/content/_index.md @@ -0,0 +1,8 @@ +--- +title: "Cate Conners" +description: "TODO: one-line author bio/description" +--- + +TODO: Cate's homepage copy goes here. + +Get a free short story: [download the reader magnet](/gift). diff --git a/content/contact.md b/content/contact.md new file mode 100644 index 0000000..830cdbc --- /dev/null +++ b/content/contact.md @@ -0,0 +1,5 @@ +--- +title: "Contact" +layout: "contact" +description: "Get in touch with Cate Conners." +--- diff --git a/content/gift-thanks/index.md b/content/gift-thanks/index.md new file mode 100644 index 0000000..acec709 --- /dev/null +++ b/content/gift-thanks/index.md @@ -0,0 +1,13 @@ +--- +title: "Thanks! Here's your story." +description: "TODO: matches whatever the real story ends up being called." +--- + + + +Thanks for signing up. Your story is on its way — check your inbox for the +download link. + +Talk soon, + +Cate diff --git a/content/gift/index.md b/content/gift/index.md new file mode 100644 index 0000000..2cd427e --- /dev/null +++ b/content/gift/index.md @@ -0,0 +1,45 @@ +--- +title: "Get A Story" +description: "TODO: one to two sentence back-cover-style hook for the free short story. Fantasy Michigan — think old growth forest, a lake that remembers, something waiting under the water. Written by Cate Conners." +type: "courses" +eec_slug: "reader-magnet" + +# Optional italic line under the title, for handling an objection up front. +hero_subhead: "This story, occasional emails when something new comes out and other things you want in your inbox." + +# Label on the signup form's submit button. +cta_text: "Send me the story" + +# Cover image, hosted in the public downloads bucket alongside the epub. +# Used as the on-page hero image. +# TODO: replace with the real cover once it exists — see downloads-uploader. +cover_image: "https://downloads.cateconners.com/reader-magnet/cover.jpg" + +# Optional: a landscape (1200x630) variant of the cover for social-share +# previews (og:image/Twitter card). Falls back to cover_image if unset -- +# see layouts/_default/baseof.html. +social_image: "https://downloads.cateconners.com/reader-magnet/cover_social.jpg" + +# Direct link to the epub in the public downloads bucket — shown as an +# on-page button immediately after a successful signup, independent of the +# welcome email (see layouts/partials/course-enroll-form.html). +# TODO: replace with the real epub link once the story is ready. +download_url: "https://downloads.cateconners.com/reader-magnet/story.epub" +download_text: "Download now" + +# Optional heading above the repeated signup form at the bottom of the page. +repeat_cta_heading: "Want the story?" +--- + + +Somewhere north of the tree line, where the maples go quiet and the lake +keeps its own counsel, something has been waiting a long time to be found. + +Sign up to my mailing list and I'll send you a free short story — a little +Fantasy Michigan magic for your inbox, plus occasional word when something +new comes out. diff --git a/content/privacy.md b/content/privacy.md new file mode 100644 index 0000000..90cdfbc --- /dev/null +++ b/content/privacy.md @@ -0,0 +1,62 @@ +--- +title: "Privacy Policy" +description: "How cateconners.com collects, uses, and protects your information." +--- + +_Last updated: July 2026_ + +This policy explains what information this site collects through use of +cateconners.com, and what happens to it. + +## What this site collects + +Depending on how you use the site, that may include: + +- Your name, email address, and message, when you submit the contact form. +- Your email address, if you sign up for the free short story. +- Your email address and name, if given, if you opt in to ongoing updates. + +This site doesn't use third-party analytics, ad trackers, or tracking +pixels. Everything above runs on infrastructure operated directly — +nothing is handed to a third-party marketing platform. + +## Why this site collects it + +- To respond to messages sent through the contact form. +- To deliver the short story you signed up for. +- To send periodic updates to people who've explicitly opted in — never + as a side effect of simply using the site. + +## What this site doesn't do + +- This site doesn't sell or rent your information to anyone. +- This site doesn't share it with third parties except the infrastructure + providers necessary to run the site and send email (e.g. the hosting + provider and its network) — none of whom get to use it for their own + purposes. + +## Your control over it + +- **Unsubscribe**: every ongoing-update email includes an unsubscribe + link. Clicking it removes you from that list immediately. +- **The free short story**: this is a one-off signup with no ongoing + subscription — once you've received it, there's nothing further to opt + out of unless you also joined the ongoing updates list. +- **Deletion**: reach out through [the contact form](/contact/) and + your information will be deleted from these systems. + +## Security + +Your information is stored on infrastructure controlled directly, not +handed off to third-party SaaS platforms. That doesn't make it +invulnerable — no system is — but it does mean there's exactly one place +your data lives, and one party accountable for it. + +## Changes + +If this policy changes in a way that matters, the date at the top of this +page will be updated. + +## Questions + +[Get in touch](/contact/). diff --git a/content/terms.md b/content/terms.md new file mode 100644 index 0000000..6e6c7f5 --- /dev/null +++ b/content/terms.md @@ -0,0 +1,39 @@ +--- +title: "Terms of Use" +description: "Terms for using cateconners.com and its free short story." +--- + +_Last updated: July 2026_ + +These terms cover using cateconners.com, its free short story, and +submitting the contact form. + +## Using this site + +This site and its free short story are provided as-is, for entertainment +purposes. + +## The short story + +The story belongs to Cate Conners. You're welcome to read and enjoy it; +you're not entitled to redistribute, resell, or republish it without +asking first. + +## No warranty + +This site and its content are provided without warranties of any kind, +express or implied. + +## Liability + +To the extent the law allows, Cate Conners isn't liable for damages +arising from your use of this site or its content. + +## Changes + +These terms may be updated as the site changes. Material changes will +update the date at the top of this page. + +## Questions + +[Get in touch](/contact/). diff --git a/hugo.toml b/hugo.toml new file mode 100644 index 0000000..6b7188c --- /dev/null +++ b/hugo.toml @@ -0,0 +1,30 @@ +baseURL = "https://cateconners.com/" +locale = "en-US" +title = "Cate Conners" + +enableRobotsTXT = true +buildDrafts = false +buildFuture = false +buildExpired = false +disableKinds = ["taxonomy", "term"] + +[params] + env = "production" + description = "TODO: one-line author bio/description" + author = "Cate Conners" + eec_base_url = "https://eec.cateconners.com" + +[[menu.footer]] + name = "Contact" + url = "/contact/" + weight = 10 + +[[menu.footer]] + name = "Privacy" + url = "/privacy/" + weight = 20 + +[[menu.footer]] + name = "Terms" + url = "/terms/" + weight = 30 diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html new file mode 100644 index 0000000..7a29dae --- /dev/null +++ b/layouts/_default/baseof.html @@ -0,0 +1,47 @@ + + + + + + {{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} · {{ .Site.Title }}{{ end }} + + {{ with .Params.social_image | default .Params.cover_image }} + + + + + {{ end }} + {{ $css := resources.Get "css/main.css" }} + {{ if hugo.IsProduction }} + {{ $css = $css | minify | fingerprint }} + + {{ else }} + + {{ end }} + + + + + + +
+ {{ block "main" . }}{{ end }} +
+ +
+ © {{ now.Year }} {{ .Site.Title }} + +
+ + diff --git a/layouts/_default/contact.html b/layouts/_default/contact.html new file mode 100644 index 0000000..f3b5710 --- /dev/null +++ b/layouts/_default/contact.html @@ -0,0 +1,60 @@ +{{ define "main" }} +

Get in touch

+

Tell me a bit about what you're working on — I read every message myself.

+ +
+ + + + + + + + + + + + +
+

+ + +{{ end }} diff --git a/layouts/_default/list.html b/layouts/_default/list.html new file mode 100644 index 0000000..426e8db --- /dev/null +++ b/layouts/_default/list.html @@ -0,0 +1,9 @@ +{{ define "main" }} +

{{ .Title }}

+{{ .Content }} + +{{ end }} diff --git a/layouts/_default/single.html b/layouts/_default/single.html new file mode 100644 index 0000000..17b274b --- /dev/null +++ b/layouts/_default/single.html @@ -0,0 +1,4 @@ +{{ define "main" }} +

{{ .Title }}

+{{ .Content }} +{{ end }} diff --git a/layouts/courses/single.html b/layouts/courses/single.html new file mode 100644 index 0000000..812a0bb --- /dev/null +++ b/layouts/courses/single.html @@ -0,0 +1,43 @@ +{{ define "main" }} +
+

{{ .Title }}

+ {{ with .Params.cover_image }}{{ $.Title }} cover{{ end }} + {{ with .Params.hero_subhead }}

{{ . }}

{{ end }} + + {{ partial "course-enroll-form.html" (dict "id" "top" "page" .) }} + + {{ with .Params.social_proof }}

{{ . }}

{{ end }} +
+ +{{ if or .Params.credibility_heading .Params.credibility_bullets }} +
+ {{ with .Params.credibility_heading }}

{{ . }}

{{ end }} + {{ with .Params.credibility_bullets }} +
    + {{ range . }}
  • {{ . | markdownify }}
  • {{ end }} +
+ {{ end }} +
+{{ end }} + +{{ if or .Params.curriculum_heading .Params.curriculum_items }} +
+ {{ with .Params.curriculum_heading }}

{{ . }}

{{ end }} + {{ with .Params.curriculum_intro }}

{{ . }}

{{ end }} + {{ with .Params.curriculum_items }} +
    + {{ range . }} +
  1. {{ with .label }}{{ . }} {{ end }}{{ .text | markdownify }}
  2. + {{ end }} +
+ {{ end }} +
+{{ end }} + +{{ .Content }} + +
+ {{ with .Params.repeat_cta_heading }}

{{ . }}

{{ end }} + {{ partial "course-enroll-form.html" (dict "id" "bottom" "page" .) }} +
+{{ end }} diff --git a/layouts/index.html b/layouts/index.html new file mode 100644 index 0000000..17b274b --- /dev/null +++ b/layouts/index.html @@ -0,0 +1,4 @@ +{{ define "main" }} +

{{ .Title }}

+{{ .Content }} +{{ end }} diff --git a/layouts/partials/course-enroll-form.html b/layouts/partials/course-enroll-form.html new file mode 100644 index 0000000..e0dcd76 --- /dev/null +++ b/layouts/partials/course-enroll-form.html @@ -0,0 +1,55 @@ +{{ $id := .id }} +{{ $page := .page }} +
+ + + + + + +
+

+{{ with $page.Params.download_url }} + +{{ end }} + + diff --git a/static/fonts/EBGaramond-Medium.woff2 b/static/fonts/EBGaramond-Medium.woff2 new file mode 100644 index 0000000..6dcab99 Binary files /dev/null and b/static/fonts/EBGaramond-Medium.woff2 differ diff --git a/static/fonts/EBGaramond-Regular.woff2 b/static/fonts/EBGaramond-Regular.woff2 new file mode 100644 index 0000000..6567c0f Binary files /dev/null and b/static/fonts/EBGaramond-Regular.woff2 differ diff --git a/static/fonts/Inter-Regular.woff2 b/static/fonts/Inter-Regular.woff2 new file mode 100644 index 0000000..41f6888 Binary files /dev/null and b/static/fonts/Inter-Regular.woff2 differ diff --git a/static/fonts/SourceSerif4-Italic.woff2 b/static/fonts/SourceSerif4-Italic.woff2 new file mode 100644 index 0000000..f1d4ac7 Binary files /dev/null and b/static/fonts/SourceSerif4-Italic.woff2 differ diff --git a/static/fonts/SourceSerif4-Regular.woff2 b/static/fonts/SourceSerif4-Regular.woff2 new file mode 100644 index 0000000..12f5b10 Binary files /dev/null and b/static/fonts/SourceSerif4-Regular.woff2 differ