mirror of
https://github.com/Patiun/com.gregkilmer.www.git
synced 2026-11-08 04:08:24 +00:00
Initial Commit
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* animations.css
|
||||
* ---------------------------------------------------------------------------
|
||||
* Keyframes used by hero backgrounds, decorative motion, and ambient effects.
|
||||
* All animations are disabled by the global prefers-reduced-motion rule in
|
||||
* global.css, so they are safe to use freely.
|
||||
*/
|
||||
|
||||
/* Slowly drift radial-gradient stops for the hero mesh background. */
|
||||
@keyframes gradient-shift {
|
||||
0% {
|
||||
background-position:
|
||||
0% 0%,
|
||||
100% 0%,
|
||||
50% 100%;
|
||||
}
|
||||
50% {
|
||||
background-position:
|
||||
100% 100%,
|
||||
0% 100%,
|
||||
50% 0%;
|
||||
}
|
||||
100% {
|
||||
background-position:
|
||||
0% 0%,
|
||||
100% 0%,
|
||||
50% 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Soft vertical float for decorative shapes / icons. */
|
||||
@keyframes float {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Slow rotation for ambient rings. */
|
||||
@keyframes spin-slow {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Pulsing glow for accent dots / status indicators. */
|
||||
@keyframes pulse-glow {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.15);
|
||||
}
|
||||
}
|
||||
|
||||
/* Nav backdrop blur is handled via class toggle, but the entry of the nav uses
|
||||
* a fade-down so it doesn't pop on first paint. */
|
||||
@keyframes fade-down {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-12px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Utility classes bound to the keyframes above. */
|
||||
.animate-gradient-shift {
|
||||
background-size: 200% 200%;
|
||||
animation: gradient-shift 18s var(--ease-in-out-soft, ease-in-out) infinite;
|
||||
}
|
||||
.animate-float {
|
||||
animation: float 6s var(--ease-in-out-soft, ease-in-out) infinite;
|
||||
}
|
||||
.animate-spin-slow {
|
||||
animation: spin-slow 40s linear infinite;
|
||||
}
|
||||
.animate-pulse-glow {
|
||||
animation: pulse-glow 4s var(--ease-in-out-soft, ease-in-out) infinite;
|
||||
}
|
||||
.animate-fade-down {
|
||||
animation: fade-down 0.5s var(--ease-out-soft, ease-out) both;
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
/**
|
||||
* global.css
|
||||
* ---------------------------------------------------------------------------
|
||||
* Tailwind v4 entry + global base layer for Greg Kilmer's profile site.
|
||||
*
|
||||
* Tailwind v4 uses a CSS-first config. The `@import "tailwindcss"` pulls in the
|
||||
* engine and the `@theme` block below exposes the project palette as utility
|
||||
* classes (e.g. `bg-surface`, `text-accent`). Section/light-dark tokens live in
|
||||
* themes.css and are referenced here via CSS variables.
|
||||
*
|
||||
* Import order matters: theme tokens first (so @theme can reference them), then
|
||||
* Tailwind, then animations, then base.
|
||||
*/
|
||||
|
||||
/* Palette + light/dark + section surface tokens (CSS variables). */
|
||||
@import "./themes.css";
|
||||
/* Keyframes (gradient shift, fade-up, blur, float). */
|
||||
@import "./animations.css";
|
||||
|
||||
/* Tailwind engine. In v4 this is a single import. */
|
||||
@import "tailwindcss";
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Theme bridge: expose CSS variables from themes.css to Tailwind utilities.
|
||||
* Each entry below generates a utility family, e.g. `--color-surface` -> the
|
||||
* `bg-surface` / `text-surface` / `border-surface` classes.
|
||||
* The values point at the CSS variables so that changing `data-theme` or the
|
||||
* section wrapper (`data-surface`) instantly restyles every component.
|
||||
* ------------------------------------------------------------------------- */
|
||||
@theme {
|
||||
/* Brand colors (fixed, do not change with theme). Night-sky mystical set:
|
||||
* star gold, mystic violet, nebula teal, midnight indigo, star-white. */
|
||||
--color-amber: #e8b54a; /* star gold */
|
||||
--color-amber-soft: #f4cd72; /* brighter gold for hovers */
|
||||
--color-violet: #8b7bd8; /* mystic violet */
|
||||
--color-violet-deep: #6f5fb6;
|
||||
--color-teal: #6fb6ad; /* nebula teal */
|
||||
--color-teal-deep: #4f8c84;
|
||||
--color-slate: #161b3a; /* deep indigo-slate */
|
||||
--color-ink: #0b1026; /* midnight indigo */
|
||||
--color-cream: #f4f1e8; /* star-white */
|
||||
|
||||
/* Semantic tokens (resolve per theme/section via variables). */
|
||||
--color-surface: var(--surface);
|
||||
--color-surface-raised: var(--surface-raised);
|
||||
--color-surface-sunken: var(--surface-sunken);
|
||||
--color-text: var(--text);
|
||||
--color-text-muted: var(--text-muted);
|
||||
--color-text-subtle: var(--text-subtle);
|
||||
--color-border: var(--border);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-contrast: var(--accent-contrast);
|
||||
--color-secondary: var(--secondary);
|
||||
|
||||
/* Typography. */
|
||||
--font-sans: "Plus Jakarta Sans Variable", "Inter Variable", ui-sans-serif,
|
||||
system-ui, sans-serif;
|
||||
--font-body: "Inter Variable", ui-sans-serif, system-ui, sans-serif;
|
||||
--font-mono: "JetBrains Mono Variable", ui-monospace, "SFMono-Regular", monospace;
|
||||
|
||||
/* Motion tokens (used across reveal + transition classes). */
|
||||
--ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
|
||||
--ease-in-out-soft: cubic-bezier(0.65, 0, 0.35, 1);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Base layer: resets, defaults, focus styles.
|
||||
* ------------------------------------------------------------------------- */
|
||||
@layer base {
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
/* Offset so anchor jumps clear the fixed nav. */
|
||||
scroll-padding-top: 6rem;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
/* Honor reduced motion globally. */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.001ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.001ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-surface);
|
||||
color: var(--color-text);
|
||||
font-family: var(--font-body);
|
||||
line-height: 1.65;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
min-height: 100dvh;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 700;
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* Strong typographic rhythm for headings. */
|
||||
h1 {
|
||||
font-size: clamp(2.25rem, 5vw + 1rem, 4rem);
|
||||
}
|
||||
h2 {
|
||||
font-size: clamp(1.75rem, 2.5vw + 1rem, 2.5rem);
|
||||
}
|
||||
h3 {
|
||||
font-size: clamp(1.25rem, 1vw + 1rem, 1.5rem);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-accent);
|
||||
text-decoration: none;
|
||||
transition: color 0.2s var(--ease-out-soft);
|
||||
}
|
||||
a:hover {
|
||||
color: var(--color-amber-soft);
|
||||
}
|
||||
|
||||
/* Visible, on-brand focus ring for keyboard users. */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--color-amber);
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
/* Better default for images: never overflow, lazy by default in components. */
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Component-ish utilities used across the site. Keeping them here (rather than
|
||||
* as repeated Tailwind class strings) makes tone adjustments easy.
|
||||
* ------------------------------------------------------------------------- */
|
||||
@layer components {
|
||||
/* Content width container. */
|
||||
.container-prose {
|
||||
width: 100%;
|
||||
max-width: 72rem;
|
||||
margin-inline: auto;
|
||||
padding-inline: 1.25rem;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.container-prose {
|
||||
padding-inline: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* The recurring "card" surface. */
|
||||
.card {
|
||||
background-color: var(--color-surface-raised);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
transition:
|
||||
transform 0.3s var(--ease-out-soft),
|
||||
box-shadow 0.3s var(--ease-out-soft),
|
||||
border-color 0.3s var(--ease-out-soft);
|
||||
}
|
||||
.card-interactive:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 18px 40px -24px rgba(11, 16, 38, 0.55);
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* Pill / tag. */
|
||||
.pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.25rem 0.7rem;
|
||||
border-radius: 999px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
background: color-mix(in srgb, var(--color-accent) 15%, transparent);
|
||||
color: var(--color-text);
|
||||
border: 1px solid color-mix(in srgb, var(--color-accent) 35%, transparent);
|
||||
}
|
||||
|
||||
/* Primary (amber) and secondary (outlined) CTA buttons. */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.6rem;
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
transform 0.2s var(--ease-out-soft),
|
||||
box-shadow 0.2s var(--ease-out-soft),
|
||||
background-color 0.2s var(--ease-out-soft),
|
||||
border-color 0.2s var(--ease-out-soft);
|
||||
}
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
.btn-primary {
|
||||
background: var(--color-amber);
|
||||
color: var(--color-ink);
|
||||
box-shadow: 0 10px 30px -12px rgba(245, 158, 11, 0.7);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background: var(--color-amber-soft);
|
||||
color: var(--color-ink);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.btn-secondary {
|
||||
background: transparent;
|
||||
color: var(--color-text);
|
||||
border: 1.5px solid var(--color-border);
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
border-color: var(--color-accent);
|
||||
color: var(--color-text);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Visually hidden but available to screen readers. */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------------
|
||||
* Reveal-on-scroll utility. The Reveal.astro component toggles `.is-visible`.
|
||||
*
|
||||
* The `.no-js` guard below is critical: <html> starts with class="no-js"
|
||||
* (set in BaseLayout.astro) and reveal-init.ts removes it once it has wired up
|
||||
* the observer. If JS is disabled OR the script fails to load, that class is
|
||||
* never removed — so this fallback forces reveals visible rather than leaving
|
||||
* the page blank. This is the safety net against the original invisibility bug.
|
||||
* ------------------------------------------------------------------------- */
|
||||
.no-js .reveal {
|
||||
opacity: 1 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(24px);
|
||||
transition:
|
||||
opacity 0.7s var(--ease-out-soft),
|
||||
transform 0.7s var(--ease-out-soft);
|
||||
will-change: opacity, transform;
|
||||
}
|
||||
.reveal.is-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.reveal {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* themes.css
|
||||
* ---------------------------------------------------------------------------
|
||||
* Two-axis theming model for the site:
|
||||
*
|
||||
* 1. Global light/dark mode -> set on <html data-theme="light|dark">
|
||||
* 2. Section surface tone -> set on a wrapper via data-surface:
|
||||
* data-surface="neutral" Home / About / Contact (deep night-sky)
|
||||
* data-surface="professional" Professional wing (polished obsidian + gold)
|
||||
* data-surface="interest" Interests wing (candlelit study under stars)
|
||||
*
|
||||
* The semantic tokens (--surface, --text, --accent, ...) are redefined for each
|
||||
* (data-theme x data-surface) combination below. Components only ever reference
|
||||
* the semantic tokens, so swapping either axis automatically restyles them.
|
||||
*
|
||||
* Palette direction: night-sky mystical — midnight indigo bases, star-gold
|
||||
* accents, mystic-violet secondary, nebula-teal tertiary. Light mode is
|
||||
* reinterpreted as a "twilight"/parchment variant so the toggle always works
|
||||
* without breaking the celestial mood.
|
||||
*
|
||||
* `theme-init.ts` (inlined in <head>) sets data-theme before paint to avoid a
|
||||
* flash of the wrong theme. data-surface is applied by the layout wrappers.
|
||||
*/
|
||||
|
||||
/* ===========================================================================
|
||||
* DEFAULT / NEUTRAL SURFACE (Home, About, Contact)
|
||||
* Deep midnight indigo with star-gold accents. The default night sky.
|
||||
* ======================================================================== */
|
||||
|
||||
/* Light + neutral — twilight parchment (warm, low-contrast, still celestial). */
|
||||
:root,
|
||||
[data-theme="light"][data-surface="neutral"],
|
||||
[data-theme="light"][data-surface="neutral"] body {
|
||||
--surface: #f3eee0; /* aged parchment / star-map paper */
|
||||
--surface-raised: #fbf7ec;
|
||||
--surface-sunken: #e7dfca;
|
||||
--text: #1c1830; /* deep indigo ink */
|
||||
--text-muted: #4a445e;
|
||||
--text-subtle: #6b6582;
|
||||
--border: #d8cdb0;
|
||||
--accent: #9a6b1a; /* deeper gold for AA on light */
|
||||
--accent-contrast: #fbf7ec;
|
||||
--secondary: #6f5fb6; /* mystic violet */
|
||||
}
|
||||
|
||||
/* Dark + neutral — the canonical night sky. */
|
||||
[data-theme="dark"][data-surface="neutral"],
|
||||
[data-theme="dark"][data-surface="neutral"] body {
|
||||
--surface: #0b1026; /* midnight indigo */
|
||||
--surface-raised: #161b3a; /* indigo panel */
|
||||
--surface-sunken: #070a1a; /* near-black void */
|
||||
--text: #f4f1e8; /* star-white */
|
||||
--text-muted: #c7c3df;
|
||||
--text-subtle: #8e89b5;
|
||||
--border: #2c3160;
|
||||
--accent: #e8b54a; /* star gold */
|
||||
--accent-contrast: #0b1026;
|
||||
--secondary: #8b7bd8; /* mystic violet */
|
||||
}
|
||||
|
||||
/* ===========================================================================
|
||||
* PROFESSIONAL SURFACE
|
||||
* Polished obsidian: deepest indigo with gold + subtle violet. Confident,
|
||||
* precise, the "polished wing" from the spec — now with a starlit edge.
|
||||
* ======================================================================== */
|
||||
|
||||
[data-surface="professional"] {
|
||||
--surface: #0a0e22;
|
||||
--surface-raised: #141a38;
|
||||
--surface-sunken: #05081a;
|
||||
--text: #f4f1e8;
|
||||
--text-muted: #c5c0db;
|
||||
--text-subtle: #8782ad;
|
||||
--border: #292e57;
|
||||
--accent: #e8b54a;
|
||||
--accent-contrast: #0a0e22;
|
||||
--secondary: #8b7bd8;
|
||||
}
|
||||
|
||||
/* Light professional: keeps a dignified deep tone, slightly lifted so the
|
||||
* global toggle is never jarring next to a parchment Home page. */
|
||||
[data-theme="light"][data-surface="professional"] {
|
||||
--surface: #1a1f44;
|
||||
--surface-raised: #252b54;
|
||||
--surface-sunken: #141838;
|
||||
--text: #f4f1e8;
|
||||
--text-muted: #d4d0ec;
|
||||
--text-subtle: #aaa5cd;
|
||||
--border: #3a3f72;
|
||||
}
|
||||
|
||||
/* ===========================================================================
|
||||
* INTEREST SURFACE
|
||||
* Candlelit study under a starfield: warm amber-gold bases with deep indigo
|
||||
* text by default (light), flipping to a warm brown-indigo at night. The
|
||||
* "fun wing" stays inviting, now with an esoteric warmth.
|
||||
* ======================================================================== */
|
||||
|
||||
[data-theme="light"][data-surface="interest"],
|
||||
[data-surface="interest"] {
|
||||
--surface: #f6ecd2; /* warm candlelight parchment */
|
||||
--surface-raised: #fdf5dc;
|
||||
--surface-sunken: #ecdcb4;
|
||||
--text: #241a2e; /* plum-indigo ink */
|
||||
--text-muted: #5c4a3a;
|
||||
--text-subtle: #826a52;
|
||||
--border: #ddc79a;
|
||||
--accent: #9a6b1a; /* deeper gold for AA */
|
||||
--accent-contrast: #fdf5dc;
|
||||
--secondary: #6f5fb6; /* mystic violet */
|
||||
}
|
||||
|
||||
[data-theme="dark"][data-surface="interest"] {
|
||||
--surface: #1a1320; /* warm brown-indigo night */
|
||||
--surface-raised: #261c2e;
|
||||
--surface-sunken: #120d18;
|
||||
--text: #f6ecd2; /* warm starlight */
|
||||
--text-muted: #ddcbb0;
|
||||
--text-subtle: #b09a82;
|
||||
--border: #3a2c1f;
|
||||
--accent: #e8b54a; /* star gold */
|
||||
--accent-contrast: #1a1320;
|
||||
--secondary: #b09a78; /* warm gold-bronze secondary at night */
|
||||
}
|
||||
Reference in New Issue
Block a user