/* ============================================================
   ERIC GANA
   Engineering + philosophy. One person, one visual system.

   Palette: warm paper and deep ink in light, cool slate in dark,
   one restrained petrol accent across both. Neutrals carry a hue
   cast rather than being pure grey — flat grey is what makes an
   interface read as cheap.

   Contents
     1  Fonts
     2  Tokens
     3  Reset + base
     4  Motion primitives
     5  Layout
     6  Header
     7  Hero
     8  Section + rules
     9  Index list (papers, projects)
    10  Cards
    11  Tags + labels
    12  Buttons
    13  Prose
    14  Admin
    15  Footer
   ============================================================ */

/* --- 1. FONTS ----------------------------------------------- */

@font-face {
  font-family: 'Plex Sans';
  src: url('/fonts/ibm-plex-sans-latin-wght-normal.woff2') format('woff2-variations');
  font-weight: 100 700;
  font-display: swap;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('/fonts/ibm-plex-mono-latin-400-normal.woff2') format('woff2');
  font-weight: 400; font-display: swap;
}
@font-face {
  font-family: 'Plex Mono';
  src: url('/fonts/ibm-plex-mono-latin-500-normal.woff2') format('woff2');
  font-weight: 500; font-display: swap;
}
@font-face {
  font-family: 'Plex Serif';
  src: url('/fonts/ibm-plex-serif-latin-400-normal.woff2') format('woff2');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Plex Serif';
  src: url('/fonts/ibm-plex-serif-latin-400-italic.woff2') format('woff2');
  font-weight: 400; font-style: italic; font-display: swap;
}
@font-face {
  font-family: 'Plex Serif';
  src: url('/fonts/ibm-plex-serif-latin-600-normal.woff2') format('woff2');
  font-weight: 600; font-style: normal; font-display: swap;
}


/* --- 2. TOKENS ----------------------------------------------- */

:root {
  color-scheme: light dark;

  --font-sans:  'Plex Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-mono:  'Plex Mono', ui-monospace, 'Cascadia Code', Consolas, monospace;
  --font-serif: 'Plex Serif', Georgia, 'Times New Roman', serif;

  --t-xs:   0.75rem;
  --t-sm:   0.8125rem;
  --t-base: 0.9375rem;
  --t-md:   1.0625rem;
  --t-lg:   1.1875rem;
  --t-xl:   1.5rem;
  --t-2xl:  2.125rem;
  --t-3xl:  clamp(2.5rem, 6vw, 4rem);

  /* 4px grid, extended upward. The old scale stopped at 96px and
     everything felt cramped — generous space is most of what
     separates considered from default. */
  --s-1: 0.25rem;  --s-2: 0.5rem;   --s-3: 0.75rem;  --s-4: 1rem;
  --s-5: 1.25rem;  --s-6: 1.5rem;   --s-8: 2rem;     --s-10: 2.5rem;
  --s-12: 3rem;    --s-16: 4rem;    --s-20: 5rem;    --s-24: 6rem;
  --s-32: 8rem;    --s-40: 10rem;

  --measure: 66ch;
  --page: 72rem;

  /* Rounded now, on a scale rather than one value — a card and a
     tag shouldn't share a radius or the small things look bloated. */
  --radius-sm: 6px;
  --radius:    12px;
  --radius-lg: 18px;
  --radius-pill: 999px;

  /* GLASS
     backdrop-filter blurs whatever is behind the element. Two
     things it needs to stay usable: enough background opacity
     that text keeps its contrast, and a hairline border, because
     a blurred panel with no edge reads as a smudge. */
  --glass-bg:     color-mix(in srgb, var(--raised) 72%, transparent);
  --glass-edge:   color-mix(in srgb, var(--ink) 10%, transparent);
  --glass-blur:   blur(14px) saturate(150%);

  /* MOTION
     Short, confident, never bouncy. One easing curve everywhere —
     mixing curves is what makes motion feel unplanned. */
  --dur-fast: 140ms;
  --dur:      240ms;
  --dur-slow: 480ms;
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  /* LIGHT — warm paper, warm ink.
     Every neutral sits on a warm hue, so the greys relate to each
     other instead of looking like five unrelated greys. */
  --paper:      #f7f5f0;
  --raised:     #fffdf9;
  --sunk:       #efebe3;
  --ink:        #1a1915;
  --ink-2:      #56524a;
  --ink-3:      #6e6962;
  --rule:       #e2ddd2;
  --rule-firm:  #c7c0b2;

  /* One restrained accent: deep petrol. Cool against warm paper,
     which is what keeps it from looking decorative. */
  --accent:     #15605c;
  --accent-ink: #0f4a47;
  --accent-wash:#e8f0ee;

  /* Text sitting ON the accent (filled buttons). It has to flip
     with the theme: in light mode the accent is dark so white
     works, but in dark mode the accent is a LIGHT teal and white
     on it measured 2.18:1 — effectively unreadable.
     A contrast check caught it; the eye would not have, because
     you only ever look at one theme at a time. */
  --on-accent:  #ffffff;

  --danger:     #9e2b20;

  --shadow: 0 1px 2px rgb(26 25 21 / 0.04), 0 8px 24px -12px rgb(26 25 21 / 0.12);
}

/* DARK — cool slate, not black, not inverted light. */
@media (prefers-color-scheme: dark) {
  :root {
    --paper:      #121417;
    --raised:     #191c21;
    --sunk:       #0d0f11;
    --ink:        #e7e9ec;
    --ink-2:      #a3aab3;
    --ink-3:      #848c96;
    --rule:       #262a30;
    --rule-firm:  #3a414a;

    --accent:     #5cc0b4;
    --accent-ink: #8ad8cd;
    --accent-wash:#13231f;
    --on-accent:  #0b1416;   /* dark text on the light teal: 8.4:1 */
    --danger:     #f0817a;

    --shadow: 0 1px 2px rgb(0 0 0 / 0.3), 0 8px 28px -14px rgb(0 0 0 / 0.6);
  }
}


/* --- 2b. SECTION ATMOSPHERES ---------------------------------
   One person, different rooms.

   Type, spacing, structure and layout never change. What changes
   is colour temperature, the ambient graphic behind the page, and
   the accent. That's enough to make a room feel different without
   making it a different building.

     default  — petrol. The brand. Home, admin, everything else.
     science  — cool cyan, blueprint grid, instrument light.
     library  — warm amber, lamplight, paper.
   ------------------------------------------------------------ */

/* SCIENCE — engineering and projects */
body.theme-science {
  --accent:      #0b6e7f;
  --accent-ink:  #075463;
  --accent-wash: #e4f1f4;
  --on-accent:   #ffffff;
  --ambient-1:   #7fd4e8;   /* the glow colours behind the page */
  --ambient-2:   #4aa3c7;
}
@media (prefers-color-scheme: dark) {
  body.theme-science {
    --accent:      #4dd0e8;
    --accent-ink:  #85e2f2;
    --accent-wash: #0c2730;
    --on-accent:   #061417;
    --ambient-1:   #1a5c73;
    --ambient-2:   #10364a;
  }
}

/* LIBRARY — the writing */
body.theme-library {
  --accent:      #935415;
  --accent-ink:  #6f3f0f;
  --accent-wash: #f6eddf;
  --on-accent:   #ffffff;
  --ambient-1:   #e8c489;
  --ambient-2:   #c89a5a;
}
@media (prefers-color-scheme: dark) {
  body.theme-library {
    --accent:      #e0ad64;
    --accent-ink:  #efc98d;
    --accent-wash: #2a2015;
    --on-accent:   #1a1206;
    --ambient-1:   #6b4e22;
    --ambient-2:   #43301a;
  }
}

/* Default atmosphere for every other page */
body { --ambient-1: #6fd0c4; --ambient-2: #3a8d85; }


/* --- 2c. AMBIENT MOTION --------------------------------------
   The moving background. Two slow drifting glows, plus a texture
   layer that differs per room.

   Fixed position and pointer-events:none so it never interferes
   with reading or clicking. Only transform and opacity animate,
   so it runs on the GPU and costs nothing to scroll past.      */

.ambient {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  overflow: hidden;
}

/* Two soft colour fields, drifting on long cycles at different
   speeds so they never visibly repeat a pattern. */
.ambient::before,
.ambient::after {
  content: '';
  position: absolute;
  width: 65vmax;
  height: 65vmax;
  border-radius: 50%;
  filter: blur(90px);
  opacity: 0.22;
  will-change: transform;
}

.ambient::before {
  background: radial-gradient(circle, var(--ambient-1), transparent 68%);
  top: -22vmax; left: -14vmax;
  animation: drift-a 34s ease-in-out infinite alternate;
}

.ambient::after {
  background: radial-gradient(circle, var(--ambient-2), transparent 68%);
  bottom: -26vmax; right: -16vmax;
  animation: drift-b 46s ease-in-out infinite alternate;
}

@media (prefers-color-scheme: dark) {
  .ambient::before, .ambient::after { opacity: 0.3; }
}

@keyframes drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(12vmax, 8vmax, 0) scale(1.18); }
}
@keyframes drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-10vmax, -9vmax, 0) scale(1); }
}

/* SCIENCE texture: a blueprint grid, drifting slowly upward.
   Drawn with gradients rather than an image — no extra request,
   and it recolours itself with the theme. */
body.theme-science .ambient-texture {
  position: absolute;
  inset: -10% -10% -10% -10%;
  background-image:
    linear-gradient(to right,  color-mix(in srgb, var(--ambient-2) 26%, transparent) 1px, transparent 1px),
    linear-gradient(to bottom, color-mix(in srgb, var(--ambient-2) 26%, transparent) 1px, transparent 1px);
  background-size: 48px 48px;
  opacity: 0.4;
  mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 20%, transparent 78%);
  animation: grid-drift 28s linear infinite;
}

@keyframes grid-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, -48px, 0); }   /* exactly one cell,
                                                     so the loop is seamless */
}

/* LIBRARY texture: horizontal rules like shelves, breathing
   gently rather than scrolling. */
body.theme-library .ambient-texture {
  position: absolute;
  inset: 0;
  background-image: repeating-linear-gradient(
    to bottom,
    color-mix(in srgb, var(--ambient-2) 20%, transparent) 0px,
    color-mix(in srgb, var(--ambient-2) 20%, transparent) 1px,
    transparent 1px,
    transparent 74px
  );
  opacity: 0.5;
  mask-image: radial-gradient(ellipse 90% 70% at 50% 10%, #000 10%, transparent 80%);
  animation: shelf-breathe 20s ease-in-out infinite alternate;
}

@keyframes shelf-breathe {
  from { opacity: 0.32; transform: translate3d(0, 0, 0); }
  to   { opacity: 0.55; transform: translate3d(0, 10px, 0); }
}

/* --- 2d. THE DRAWN MOTIFS ------------------------------------
   A truss and a plotted curve behind the engineering pages;
   a temple front and a lyre behind the writing.

   Two things keep them background rather than foreground:
   low opacity, and a mask that fades them out where the text
   sits. A drawing competing with prose is just noise.          */

.motif {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  color: var(--accent);
  opacity: 0.1;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* Fade out toward the centre-left, which is where the reading
     column sits. The drawing stays legible at the edges. */
  mask-image: radial-gradient(ellipse 70% 80% at 28% 42%,
              transparent 18%, #000 72%);
  animation: motif-float 50s ease-in-out infinite alternate;
  will-change: transform;
}

@media (prefers-color-scheme: dark) { .motif { opacity: 0.14; } }

@media (min-width: 64rem) { .motif { opacity: 0.13; } }

/* A long, almost imperceptible drift. Slow enough that you never
   catch it moving — you just notice the page isn't static. */
@keyframes motif-float {
  from { transform: translate3d(0, 0, 0) scale(1.02); }
  to   { transform: translate3d(-2.5%, -1.8%, 0) scale(1.08); }
}

/* Each stroke draws itself in on load.

   Note the direction: the RESTING state is drawn (offset 0), and
   the animation starts from hidden via `from`. Not the other way
   round.

   I wrote it the other way first — hidden by default, revealed by
   the animation — and the entire drawing stayed invisible in an
   environment where the animation didn't run. Same mistake as the
   scroll reveals. If an effect fails, you should lose the effect,
   never the thing it was decorating.

   `backwards` holds the hidden state during the stagger delay, so
   the build-up still reads correctly. */
/* NO draw-in animation. This is a deliberate removal, and the
   reasoning is worth keeping.

   The drawings originally drew themselves in by animating
   stroke-dashoffset from hidden to visible. It looked good in
   principle and failed in practice: in an environment where
   animations are created but never advanced, every stroke stayed
   at its starting value and the entire drawing was blank. Three
   variants were tried — `forwards`, `backwards`, and no fill with
   staggered durations — and all three froze invisible, because
   they all share the same flaw: the resting state is nothing.

   The rule this leaves behind, which applies well beyond SVG:

     Never let the ABSENCE of an animation mean the absence of
     the thing. Animate from visible to visible — position,
     scale, colour — not from nothing to something.

   The motif still moves: the slow float below animates transform,
   and if that freezes, the drawing simply sits still. You lose
   the movement, never the drawing. */

@media (prefers-reduced-motion: reduce) {
  /* Still atmospheric, no movement. The drawings appear complete
     rather than drawing themselves. */
  .ambient::before, .ambient::after,
  .ambient-texture, .motif { animation: none !important; }
}


/* --- 3. RESET + BASE ----------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  font-family: var(--font-sans);
  font-size: var(--t-base);
  line-height: 1.6;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { display: block; max-width: 100%; height: auto; }
a { color: inherit; }

h1, h2, h3 {
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -0.02em;
  text-wrap: balance;          /* stops a lone word on the last line */
}

h1 { font-size: var(--t-3xl); letter-spacing: -0.03em; }
h2 { font-size: var(--t-xl); }
h3 { font-size: var(--t-lg); letter-spacing: -0.012em; }

/* Keyboard focus. Never remove this — someone navigating by
   keyboard would have no idea where they are. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 1px;
}

/* Skip link — first thing a screen reader or keyboard user meets.
   Hidden until focused. */
.skip-link {
  position: absolute;
  left: var(--s-4);
  top: var(--s-4);
  z-index: 100;
  padding: var(--s-3) var(--s-5);
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  border-radius: var(--radius);
  transform: translateY(-150%);
  transition: transform var(--dur) var(--ease);
}
.skip-link:focus { transform: translateY(0); }

.meta, .label {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.meta-plain { text-transform: none; letter-spacing: 0.01em; }


/* --- 4. MOTION PRIMITIVES ------------------------------------
   Only transform and opacity are animated — those are the two
   properties a browser can move without re-laying out the page,
   which is the difference between smooth and juddering.

   Nothing loops. Nothing moves unprompted except a single quiet
   entrance. */

@keyframes rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* Scroll reveals.
   .reveal is VISIBLE by default. Elements are only hidden once
   site.js has added .js-reveal to <html> AND confirmed it can
   actually un-hide them again.

   This ordering matters more than the effect does. The first
   version hid elements in CSS and relied on JavaScript to show
   them — so when IntersectionObserver silently failed to fire,
   the entire home page below the hero was blank. Decoration must
   never be able to take the content with it. */
.js-reveal .reveal:not(.is-visible) {
  opacity: 0;
  transform: translateY(16px);
}

.js-reveal .reveal {
  transition: opacity var(--dur-slow) var(--ease),
              transform var(--dur-slow) var(--ease);
}

/* Staggered entrance for the first screenful, so the page
   assembles rather than appearing all at once. */
.enter   { animation: rise var(--dur-slow) var(--ease) both; }
.enter-1 { animation-delay: 60ms; }
.enter-2 { animation-delay: 120ms; }
.enter-3 { animation-delay: 190ms; }

@media (prefers-reduced-motion: reduce) {
  /* Some people get genuinely unwell from screen motion and set
     this in their OS. Content must still be fully visible. */
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
}


/* --- 5. LAYOUT ----------------------------------------------- */

.wrap, .site-header, .hero, .section, .site-footer {
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--s-6);
}
@media (min-width: 48rem) {
  .wrap, .site-header, .hero, .section, .site-footer { padding-inline: var(--s-10); }
}


/* --- 6. HEADER ----------------------------------------------- */

.site-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--s-6);
  flex-wrap: wrap;
  padding-block: var(--s-6);
  position: sticky;
  top: 0;
  z-index: 20;
  max-width: 100%;
  padding-inline: var(--s-6);
}

/* Glass bar. The inner wrap keeps the content aligned to the page
   while the glass itself spans the full width — otherwise you get
   a floating pill that detaches when you scroll. */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .site-header {
    background: var(--glass-bg);
    -webkit-backdrop-filter: var(--glass-blur);
    backdrop-filter: var(--glass-blur);
  }
}
/* Browsers without backdrop-filter get a solid bar rather than
   transparent text over scrolling content. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header { background: var(--raised); }
}

@media (min-width: 48rem) {
  .site-header { padding-inline: var(--s-10); }
}

/* Hairline that stops short of the page edge — a drawn rule
   rather than a border. */
.site-header::after {
  content: '';
  position: absolute;
  left: var(--s-6); right: var(--s-6); bottom: 0;
  height: 1px;
  background: var(--rule);
}
@media (min-width: 48rem) {
  .site-header::after { left: var(--s-10); right: var(--s-10); }
}

.logo {
  font-weight: 600;
  font-size: var(--t-base);
  letter-spacing: -0.015em;
  text-decoration: none;
  min-width: 0;
}

.site-header nav { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-6); }

.site-header nav a {
  position: relative;
  text-decoration: none;
  font-size: var(--t-sm);
  color: var(--ink-2);
  padding-block: var(--s-1);
  transition: color var(--dur-fast) var(--ease);
}
.site-header nav a:hover { color: var(--ink); }

/* Underline that grows from the left on hover — scaleX is a
   transform, so it costs nothing to animate. */
.site-header nav a::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -1px;
  height: 1.5px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur) var(--ease);
}
.site-header nav a:hover::after { transform: scaleX(1); }
.site-header nav a.active { color: var(--ink); }
.site-header nav a.active::after { transform: scaleX(1); }


/* --- 6b. LANDING ---------------------------------------------
   Portrait on one side, name and the two doors on the other.
   One screen, two choices, nothing else to decide.            */

.landing {
  display: grid;
  gap: var(--s-10);
  align-items: center;
  padding-block: var(--s-16) var(--s-12);
}

@media (min-width: 56rem) {
  .landing {
    grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
    gap: var(--s-16);
    padding-block: var(--s-24) var(--s-20);
  }
}

.portrait {
  position: relative;
  width: 100%;
  max-width: 17rem;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--glass-edge);
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow);
  animation: rise var(--dur-slow) var(--ease) both;
}

.portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-slow) var(--ease);
}

.portrait:hover img { transform: scale(1.03); }

/* Shown when no photograph has been added yet. Says what to do
   rather than leaving a grey rectangle. */
.portrait--empty {
  display: grid;
  place-items: center;
  text-align: center;
  padding: var(--s-6);
  color: var(--ink-3);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.06em;
  line-height: 1.8;
  border-style: dashed;
  border-color: var(--rule-firm);
}

.landing-body { min-width: 0; }

.landing-body h1 { margin-top: var(--s-4); }

.landing-body .subtitle {
  margin-top: var(--s-5);
  font-size: var(--t-lg);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 42ch;
}

/* THE TWO DOORS */
.doors {
  display: grid;
  gap: var(--s-4);
  margin-top: var(--s-10);
}

@media (min-width: 34rem) {
  .doors { grid-template-columns: 1fr 1fr; }
}

.door {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--s-2);
  padding: var(--s-6);
  min-height: 10rem;
  border-radius: var(--radius-lg);
  border: 1px solid var(--glass-edge);
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  text-decoration: none;
  overflow: hidden;
  isolation: isolate;
  transition: transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .door, .portrait { background: var(--raised); }
}

/* Each door previews its room's colour — so the choice is
   visible before you make it. */
.door--science { --accent: #0b6e7f; --door-wash: #e4f1f4; }
.door--library { --accent: #935415; --door-wash: #f6eddf; }

@media (prefers-color-scheme: dark) {
  .door--science { --accent: #4dd0e8; --door-wash: #0c2730; }
  .door--library { --accent: #e0ad64; --door-wash: #2a2015; }
}

/* The wash rises from the bottom on hover. A transform, so it
   costs nothing. */
.door::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--door-wash);
  transform: translateY(101%);
  transition: transform var(--dur-slow) var(--ease);
}

.door:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow);
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
}
.door:hover::after { transform: translateY(0); }

.door .door-label {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}

.door h2 { font-size: var(--t-lg); color: var(--ink); }
.door p { font-size: var(--t-sm); color: var(--ink-2); max-width: 32ch; }

.door .door-go {
  margin-top: auto;
  padding-top: var(--s-4);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}
.door .door-go::after { content: '→'; transition: transform var(--dur) var(--ease); }
.door:hover .door-go::after { transform: translateX(5px); }

/* A small drawn mark in the corner of each door, echoing the
   motif of the room it leads to. */
.door-mark {
  position: absolute;
  right: -14px;
  bottom: -14px;
  width: 7.5rem;
  height: 7.5rem;
  color: var(--accent);
  opacity: 0.16;
  z-index: -1;
  transition: transform var(--dur-slow) var(--ease), opacity var(--dur) var(--ease);
}
.door:hover .door-mark { transform: scale(1.12) rotate(-4deg); opacity: 0.3; }


/* --- 7. HERO ------------------------------------------------- */

.hero { padding-block: var(--s-24) var(--s-16); }
@media (min-width: 48rem) { .hero { padding-block: var(--s-32) var(--s-20); } }

.hero h1 { max-width: 16ch; }

.hero .subtitle {
  margin-top: var(--s-6);
  font-size: var(--t-lg);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 44ch;
  font-weight: 400;
  text-wrap: pretty;
}

.hero .intro {
  margin-top: var(--s-6);
  max-width: var(--measure);
  color: var(--ink-2);
}

.hero .eyebrow { display: block; margin-bottom: var(--s-6); color: var(--accent); }

.accent { color: var(--accent); }


/* --- 8. SECTION ---------------------------------------------- */

.section { padding-block: var(--s-20); position: relative; }
@media (min-width: 48rem) { .section { padding-block: var(--s-24); } }

.section::before {
  content: '';
  position: absolute;
  left: var(--s-6); right: var(--s-6); top: 0;
  height: 1px;
  background: var(--rule);
}
@media (min-width: 48rem) {
  .section::before { left: var(--s-10); right: var(--s-10); }
}

/* Heading with a mono index and an accent tick, like a drawing
   sheet. Set data-index="01" in the HTML. */
.section-head {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
  margin-bottom: var(--s-12);
  flex-wrap: wrap;
}
.section-head .index {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.12em;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.section-head h2 { flex: 1 1 auto; min-width: 0; }
.section-head .aside { color: var(--ink-3); font-size: var(--t-sm); }


/* --- 9. INDEX LIST -------------------------------------------
   A ruled list rather than a grid of boxes. Reads as a contents
   page — more editorial, and it suits long titles far better. */

.index-list { border-top: 1px solid var(--rule); }

.index-item {
  display: grid;
  grid-template-columns: 2.5rem 1fr;
  gap: var(--s-4);
  padding: var(--s-6) var(--s-4);
  border-bottom: 1px solid var(--rule);
  border-radius: var(--radius);
  position: relative;
  transition: background var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}

/* An accent bar that grows down the left edge on hover. */
.index-item::before {
  content: '';
  position: absolute;
  left: 0; top: 10%; bottom: 10%;
  width: 2px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  transform: scaleY(0);
  transform-origin: center;
  transition: transform var(--dur) var(--ease);
}
.index-item:hover::before { transform: scaleY(1); }
.index-item:hover { transform: translateX(4px); }
@media (min-width: 48rem) {
  .index-item { grid-template-columns: 3.5rem 1fr auto; align-items: start; gap: var(--s-8); }
}

.index-item:hover { background: var(--accent-wash); }

.index-item .num {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  padding-top: 0.35em;
  transition: color var(--dur-fast) var(--ease);
}
.index-item:hover .num { color: var(--accent); }

.index-item h3 { margin-bottom: var(--s-2); }

.index-item h3 a {
  text-decoration: none;
  background-image: linear-gradient(var(--accent), var(--accent));
  background-size: 0% 1.5px;
  background-repeat: no-repeat;
  background-position: left 100%;
  transition: background-size var(--dur) var(--ease), color var(--dur-fast) var(--ease);
}
.index-item h3 a:hover { color: var(--accent); background-size: 100% 1.5px; }

.index-item .summary { color: var(--ink-2); max-width: 54ch; text-wrap: pretty; }
.index-item .side { color: var(--ink-3); }

@media (min-width: 48rem) {
  .index-item .side { text-align: right; min-width: 8rem; padding-top: 0.3em; }
}

.index-item .foot { display: flex; flex-wrap: wrap; gap: var(--s-2); margin-top: var(--s-4); }


/* --- 10. CARDS ----------------------------------------------- */

.card-grid {
  display: grid;
  /* min(16rem, 100%) rather than a bare 16rem.
     A plain minmax(16rem, 1fr) tells each column it may never be
     narrower than 256px — so on a phone narrower than that the
     grid overflows and the whole page scrolls sideways. min()
     lets the track collapse to the space available instead.
     Caught by measuring: 280px of content in a 264px viewport. */
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
  gap: var(--s-5);
}

.card {
  position: relative;
  background: var(--glass-bg);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-edge);
  border-radius: var(--radius-lg);
  padding: var(--s-6);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  min-width: 0;
  overflow: hidden;
  transition: transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .card { background: var(--raised); border-color: var(--rule); }
}

/* A light sweep across the top edge — the detail that makes glass
   read as glass rather than as a translucent rectangle. */
.card::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: linear-gradient(
    to right,
    transparent,
    color-mix(in srgb, var(--accent) 55%, transparent),
    transparent
  );
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}

.card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow);
  border-color: color-mix(in srgb, var(--accent) 35%, transparent);
}

.card:hover::before { opacity: 1; }

.card h3 { text-wrap: pretty; }
.card h3 a, .card-title-link {
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease);
}
.card h3 a:hover, .card-title-link:hover { color: var(--accent); }
.card p { color: var(--ink-2); }

.card-link {
  margin-top: auto;
  padding-top: var(--s-4);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
}
/* Arrow slides on hover — a tiny bit of life, costs one transform */
.card-link::after {
  content: '→';
  transition: transform var(--dur) var(--ease);
}
.card-link:hover::after { transform: translateX(4px); }

.price {
  font-family: var(--font-mono);
  font-size: var(--t-xl);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}

.project-cover {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: var(--radius);
  margin-bottom: var(--s-2);
}
.project-cover--empty {
  display: grid;
  place-items: center;
  background: var(--sunk);
  border: 1px dashed var(--rule-firm);
  color: var(--ink-3);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}


/* --- 11. TAGS ------------------------------------------------ */

.tag-row { display: flex; flex-wrap: wrap; gap: var(--s-2); }

.tag {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.03em;
  color: var(--ink-2);
  border: 1px solid var(--rule-firm);
  border-radius: var(--radius-pill);   /* small things want a
                                          tighter shape than cards,
                                          or they look inflated */
  padding: 4px var(--s-3);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease);
}
a.tag:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-wash);
}

.card-tags, .article-tags { margin-top: var(--s-4); }
.article-tags { margin-top: var(--s-10); }


/* --- 12. BUTTONS --------------------------------------------- */

.button-row { display: flex; flex-wrap: wrap; gap: var(--s-3); }
.article-actions { margin-top: var(--s-10); }

.button {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  padding: var(--s-4) var(--s-6);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--on-accent);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
/* Interactive states increase contrast, never reduce it */
.button:hover { background: var(--accent-ink); border-color: var(--accent-ink); }
.button:active { transform: translateY(1px); }

.button-outline {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule-firm);
}
.button-outline:hover {
  background: var(--accent-wash);
  border-color: var(--accent);
  color: var(--accent);
}

.back-link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  margin-top: var(--s-16);
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-2);
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease), transform var(--dur) var(--ease);
}
.back-link:hover { color: var(--accent); transform: translateX(-3px); }


/* --- 13. PROSE ----------------------------------------------- */

.article-head { max-width: var(--measure); }

.prose {
  font-family: var(--font-serif);
  font-size: var(--t-md);
  line-height: 1.75;
  max-width: var(--measure);
  color: var(--ink);
}

.prose p { margin-bottom: 1.4em; text-wrap: pretty; }

.prose > p:first-of-type::first-line {
  /* A quiet editorial signal that the essay has begun. */
  font-variant-caps: all-small-caps;
  letter-spacing: 0.04em;
}

.prose h2, .prose h3 {
  font-family: var(--font-sans);
  margin-top: 2.6em;
  margin-bottom: 0.8em;
  line-height: 1.25;
  scroll-margin-top: var(--s-10);   /* so anchor links don't tuck
                                       headings under the top edge */
}
.prose h2 { font-size: var(--t-lg); }
.prose h3 { font-size: var(--t-md); }
.prose h2:first-child, .prose h3:first-child { margin-top: 0; }

/* A hairline above each section heading — structure made visible */
.prose h2::before {
  content: '';
  display: block;
  width: var(--s-10);
  height: 1px;
  background: var(--accent);
  margin-bottom: var(--s-5);
}

.prose ul, .prose ol { margin: 0 0 1.4em 1.4em; }
.prose li { margin-bottom: 0.55em; }

.prose blockquote {
  margin: 2em 0;
  padding-left: var(--s-6);
  border-left: 2px solid var(--accent);
  color: var(--ink-2);
  font-style: italic;
}

.prose a {
  color: var(--accent);
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  transition: color var(--dur-fast) var(--ease);
}
.prose a:hover { color: var(--accent-ink); }

.prose code {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--sunk);
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  padding: 1px 5px;
}

.prose hr { border: none; border-top: 1px solid var(--rule); margin: 3em 0; }
.prose img { border-radius: var(--radius); }

.note {
  font-family: var(--font-sans);
  font-size: var(--t-sm);
  background: var(--accent-wash);
  border-left: 2px solid var(--accent);
  padding: var(--s-4) var(--s-6);
  margin-bottom: var(--s-10);
  border-radius: 0 var(--radius) var(--radius) 0;
}
.note--error { border-left-color: var(--danger); background: transparent; }

/* Empty state — a considered message, not a blank page */
/* Spacing helpers, because style="..." is now blocked by the
   Content-Security-Policy — deliberately. If you need a gap,
   name it here rather than inlining it. */
.lead-actions  { margin-top: var(--s-8); }
.section-intro { max-width: var(--measure); color: var(--ink-2); }

.empty {
  padding: var(--s-16) 0;
  color: var(--ink-3);
  border-top: 1px solid var(--rule);
}
.empty strong { display: block; color: var(--ink-2); margin-bottom: var(--s-2); font-weight: 500; }


/* --- 13a. ABOUT ---------------------------------------------- */

.about-layout { display: grid; gap: var(--s-10); }

@media (min-width: 58rem) {
  .about-layout {
    /* Portrait on the right, text on the left. The reading column
       keeps its measure; the portrait takes what's left. */
    grid-template-columns: minmax(0, 1fr) 15rem;
    gap: var(--s-16);
    align-items: start;
  }
  .about-portrait { order: 2; position: sticky; top: var(--s-24); }
  .about-layout .prose { order: 1; }
}

.about-portrait img {
  width: 100%;
  max-width: 15rem;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--shadow);
}


/* --- 13b. PROJECT PAGES --------------------------------------- */

/* The facts strip: year, role, tools, location */
.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: var(--s-6);
  padding: var(--s-6) 0;
  margin-bottom: var(--s-10);
  border-block: 1px solid var(--rule);
}
.facts dt { margin-bottom: var(--s-2); }
.facts dd { margin: 0; color: var(--ink); font-size: var(--t-base); }

.shot { margin: 0 0 var(--s-8); }

.shot img {
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  border: 1px solid var(--rule);
  background: var(--raised);
}

.shot-link {
  display: block;
  overflow: hidden;
  border-radius: var(--radius);
}
.shot-link img { transition: transform var(--dur-slow) var(--ease); }
.shot-link:hover img { transform: scale(1.015); }

.shot figcaption { margin-top: var(--s-3); }

.shot--lead { margin-bottom: var(--s-12); }

.shot-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
  gap: var(--s-6);
  margin-top: var(--s-12);
}
.shot-grid .shot { margin-bottom: 0; }


/* --- 14. ADMIN ----------------------------------------------- */

/* Image manager in the project editor */
.admin-images {
  list-style: none;
  padding: 0;
  margin: var(--s-6) 0 0;
  display: grid;
  gap: var(--s-4);
}

.admin-image {
  display: grid;
  grid-template-columns: 160px 1fr auto auto;
  gap: var(--s-4);
  align-items: center;
  padding: var(--s-3);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--raised);
}

@media (max-width: 48rem) {
  .admin-image { grid-template-columns: 1fr; }
}

.admin-image img {
  width: 160px;
  height: auto;
  border-radius: var(--radius-sm);
  border: 1px solid var(--rule);
}

.admin-image-fields {
  display: flex;
  gap: var(--s-2);
  align-items: center;
  flex-wrap: wrap;
}
.admin-image-fields input[type="text"] {
  flex: 1 1 14rem;
  min-width: 0;
  font: inherit;
  font-size: var(--t-sm);
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--rule-firm);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
}
.admin-image-fields input[type="number"] {
  width: 4.5rem;
  font: inherit;
  font-size: var(--t-sm);
  padding: var(--s-2);
  border: 1px solid var(--rule-firm);
  border-radius: var(--radius-sm);
  background: var(--paper);
  color: var(--ink);
}
.admin-image-fields button {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: var(--s-2) var(--s-3);
  border: 1px solid var(--rule-firm);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--ink-2);
  cursor: pointer;
}
.admin-image-fields button:hover { border-color: var(--accent); color: var(--accent); }


.admin-form { display: grid; gap: var(--s-6); max-width: 40rem; }
.admin-form label { display: grid; gap: var(--s-2); font-size: var(--t-sm); font-weight: 500; }

.admin-form input[type="text"], .admin-form input[type="url"],
.admin-form input[type="number"], .admin-form input[type="password"],
.admin-form select, .admin-form textarea {
  font: inherit;
  font-size: var(--t-base);
  padding: var(--s-3) var(--s-4);
  border: 1px solid var(--rule-firm);
  border-radius: var(--radius);
  background: var(--raised);
  color: var(--ink);
  width: 100%;
  transition: border-color var(--dur-fast) var(--ease);
}
.admin-form input:hover, .admin-form textarea:hover, .admin-form select:hover {
  border-color: var(--ink-3);
}
.admin-form textarea { resize: vertical; }
.admin-form textarea.mono { font-family: var(--font-mono); font-size: var(--t-sm); line-height: 1.7; }

.admin-form .hint {
  font-family: var(--font-mono);
  font-size: var(--t-xs);
  font-weight: 400;
  color: var(--ink-3);
  text-transform: none;
  letter-spacing: 0.01em;
  line-height: 1.5;
}
.admin-form .checkbox { display: flex; align-items: center; gap: var(--s-3); font-weight: 400; }
.admin-form .checkbox input { width: auto; }
.admin-form button { justify-self: start; }

.admin-table { width: 100%; border-collapse: collapse; font-size: var(--t-sm); }
.admin-table td {
  padding: var(--s-5) var(--s-3);
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}
.admin-table tr { transition: background var(--dur-fast) var(--ease); }
.admin-table tbody tr:hover { background: var(--accent-wash); }
.admin-table td:first-child { padding-left: var(--s-2); }
.admin-table td:last-child { padding-right: var(--s-2); text-align: right; }

.row-actions {
  display: flex; gap: var(--s-4); justify-content: flex-end; align-items: center;
  font-family: var(--font-mono); font-size: var(--t-xs);
  text-transform: uppercase; letter-spacing: 0.08em;
}
.row-actions a { color: var(--accent); text-decoration: none; }
.row-actions a:hover { text-decoration: underline; text-underline-offset: 3px; }
.row-actions form { display: inline; }

.link-danger {
  background: none; border: none; padding: 0; font: inherit;
  color: var(--danger); cursor: pointer;
}
.link-danger:hover { text-decoration: underline; text-underline-offset: 3px; }

.admin-bar {
  display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap;
  margin-bottom: var(--s-10); font-size: var(--t-sm); color: var(--ink-2);
}
.admin-bar form { display: inline; }
.admin-bar button {
  background: none; border: none; padding: 0; font: inherit;
  color: var(--ink-3); cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
  transition: color var(--dur-fast) var(--ease);
}
.admin-bar button:hover { color: var(--accent); }


/* --- 15. FOOTER ---------------------------------------------- */

.site-footer {
  padding-block: var(--s-16) var(--s-20);
  margin-top: var(--s-12);
  border-top: 1px solid var(--rule);
  color: var(--ink-3);
  font-size: var(--t-sm);
}
.footer-label { display: block; margin-bottom: var(--s-4); }
.footer-links { margin-bottom: var(--s-8); }
.footer-links .tag { text-decoration: none; }
