:root {
  --ink: #16161d;
  --bg: #0a0b12;
  --bg-2: #171a2b;
  --text: #e9eaf2;
  --muted: #8a90ad;
  --border-w: 3px;
  /* Space Grotesk carries the page; Space Mono handles the label-ish text --
     ribbon flags, date stamps, section headings, the footer -- so those read
     as tags stuck on the plates rather than more prose. Both fall back to a
     system stack, which matters here: the page is served off the homelab, so
     a visitor whose browser can't reach fonts.googleapis.com still gets
     something with the right proportions instead of Times. */
  --font-sans: "Space Grotesk", "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-mono: "Space Mono", ui-monospace, "SFMono-Regular", "Consolas", monospace;
  --dogear: 20px;
  --dogear-ribbon: 11px;
  /* the inner (fill) clip-path uses a slightly smaller corner cut than the
     outer (ink) clip-path so the visible border strip is the same width on
     the diagonal corner as it is on the straight edges: a 45deg cut inset by
     border-w on all sides shrinks by border-w*(2-sqrt2) ~= 0.586*border-w
     more than a straight edge does, so we subtract that back out. */
  --dogear-inner: calc(var(--dogear) - var(--border-w) * 0.586);
  --dogear-ribbon-inner: calc(var(--dogear-ribbon) - var(--border-w) * 0.586);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  background:
    radial-gradient(ellipse at 50% -10%, var(--bg-2) 0%, transparent 60%),
    var(--bg);
  font-family: var(--font-sans);
  color: var(--text);
  overflow-x: hidden;
}

/* ---------- aetherial background ---------- */

/*
  The moving field itself is procedural and lives in js/aether.js: a
  domain-warped fbm noise shader evaluated fresh every frame, so the
  pattern never tiles or repeats (a scrolled texture always eventually
  shows its seams). Everything here is just the frame around it --
  a gradient that shows if WebGL is unavailable, and a vignette that
  keeps the middle of the page calm behind the content.
*/
.aether {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}

.aether-base,
.aether-canvas,
.aether-veil {
  position: absolute;
  inset: 0;
}

/* fallback: visible only until/unless the shader canvas goes live */
.aether-base {
  background-image: linear-gradient(
    125deg,
    #0b1e2b 0%,
    #10203c 22%,
    #1c1a3a 44%,
    #221a33 62%,
    #12243a 80%,
    #0c1c2a 100%
  );
  background-size: 220% 220%;
  animation: base-pan 96s ease-in-out infinite alternate;
}

.aether-canvas {
  width: 100%;
  height: 100%;
  display: block;
  opacity: 0;
  transition: opacity 1.2s ease;
}

.aether-canvas.is-live {
  opacity: 1;
}

.aether-veil {
  background: radial-gradient(
    ellipse at 50% 42%,
    rgba(10, 11, 18, 0.58) 0%,
    rgba(10, 11, 18, 0.3) 38%,
    rgba(10, 11, 18, 0.12) 64%,
    rgba(10, 11, 18, 0.5) 100%
  );
}

@keyframes base-pan {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 30%; }
  100% { background-position: 30% 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .aether-base { animation: none; }
}

/* ---------- layout ---------- */

.hero {
  text-align: center;
  padding: 3.5rem 1rem 2rem;
}

.hero h1 {
  margin: 0;
  font-size: clamp(1.8rem, 5vw, 3rem);
  font-weight: 700;
  letter-spacing: -0.02em;
}

.hero h1 span {
  background: linear-gradient(90deg, #6fe3c8, #6fb8ff);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero h1 em {
  font-style: normal;
  color: #ff7eb6;
}

.tagline {
  margin: 0.4rem 0 0;
  color: var(--muted);
  font-size: 1rem;
}

main {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 1.5rem 4rem;
}

.section-heading {
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--muted);
  margin: 0 0 1.6rem;
  padding-left: 0.2rem;
}

.nav-section { margin-bottom: 3.5rem; }

.ribbon-row {
  display: flex;
  flex-wrap: wrap;
  gap: 2.4rem 1.8rem;
  padding-top: 1.4rem;
}

.blog-feed {
  display: flex;
  flex-direction: column;
  gap: 2.6rem;
  padding-top: 1.4rem;
}

.site-footer {
  text-align: center;
  padding: 2rem 1rem 3rem;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.8rem;
}

.empty-note {
  color: var(--muted);
  font-style: italic;
}

/* ---------- acrylic plate card ---------- */

.plate-card {
  position: relative;
  display: block;
  width: 220px;
  text-decoration: none;
  color: inherit;
  box-shadow: 0 0 14px var(--glow), 0 0 2px var(--glow);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.plate-card:hover,
.plate-card:focus-visible {
  transform: translateY(-4px);
  box-shadow: 0 0 20px var(--glow), 0 0 4px var(--glow);
}

.blog-feed .plate-card {
  width: 100%;
  cursor: default;
}

.plate-outline {
  position: relative;
  /* Cards are flex items that stretch to the tallest card in the row, so the
     plate has to fill that height too -- otherwise the leftover strip at the
     bottom is left unpainted and the dog-ear accent, which is anchored to the
     card box, detaches from the plate and floats below it. */
  display: flex;
  height: 100%;
  padding: var(--border-w);
  clip-path: polygon(
    0 0, 100% 0,
    100% calc(100% - var(--dogear)),
    calc(100% - var(--dogear)) 100%,
    0 100%
  );
}

/*
  The gradient border is painted on a pseudo-element rather than as the
  background of .plate-outline itself. An element background fills the whole
  card box, so it would sit directly behind the translucent .plate-inner and
  hide the canvas no matter how transparent that plate's own gradient is.

  Masking the fill down to just the border strip needs `mask`, and `mask` on an
  ancestor of .plate-inner would put that element in a new backdrop root -- its
  backdrop-filter would then blur only what is painted inside the card instead
  of the page behind it. A pseudo-element is a sibling in paint order, not an
  ancestor, so the mask stays clear of the plate's backdrop.
*/
.plate-outline::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  padding: var(--border-w);
  background: linear-gradient(155deg, var(--grad-1), var(--grad-2) 100%, var(--grad-3));
  /* Paint the whole box, then subtract the exact shape .plate-inner covers --
     the content box with the same 45deg corner cut -- leaving only the border
     strip, including the stretch that runs along the diagonal. 0.7071 is
     1/sqrt(2): a corner cut of size c meets a 45deg gradient line at distance
     c/sqrt(2) from the corner. */
  -webkit-mask:
    linear-gradient(315deg, transparent calc(var(--dogear-inner) * 0.7071), #000 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(315deg, transparent calc(var(--dogear-inner) * 0.7071), #000 0) content-box,
    linear-gradient(#000 0 0);
  mask-composite: exclude;
}

.plate-inner {
  flex: 1;
  clip-path: polygon(
    0 0, 100% 0,
    100% calc(100% - var(--dogear-inner)),
    calc(100% - var(--dogear-inner)) 100%,
    0 100%
  );
  background: linear-gradient(
    155deg,
    color-mix(in srgb, var(--grad-1) 67%, transparent),
    color-mix(in srgb, var(--grad-2) 67%, transparent) 55%,
    color-mix(in srgb, var(--grad-3) 67%, transparent)
  );
  backdrop-filter: blur(10px) saturate(1.4);
  -webkit-backdrop-filter: blur(10px) saturate(1.4);
  padding: 2.2rem 1.2rem 1.4rem;
  min-height: 130px;
  color: var(--ink);
}

.blog-feed .plate-inner {
  min-height: unset;
  padding: 2.6rem 1.8rem 1.8rem;
}

.plate-desc,
.plate-body {
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.45;
}

.plate-body { font-size: 1rem; }

.plate-date {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  margin-bottom: 0.6rem;
  letter-spacing: 0.5px;
}

/*
  Body text is inked with the same gradient the outline is stroked in: the
  fill is painted behind the element and then clipped to the glyphs.
  `color: transparent` is what lets that clipped fill show through, so the
  gradient has to be declared on each text element itself -- clipping happens
  against the box it is declared on, so an inherited one would be clipped to
  the parent's glyphs (there are none) and the text would come out invisible.
*/
.plate-desc,
.plate-body,
.plate-date {
  background-image: linear-gradient(
    155deg,
    var(--grad-1),
    var(--grad-2) 55%,
    var(--grad-3)
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/*
  Dog-ear fold accent: the back of the corner the plate's clip-path folded
  away. Its two straight edges sit flush with the card box, so the outer glow
  -- cast by the card's own rectangle -- runs unbroken around the fold instead
  of leaving a strip of empty corner between the two.

  The square is oversized by the border width and anchored to the corner,
  which pushes the hypotenuse past the plate's diagonal edge. It rides at the
  same negative z-index as the plate's gradient border but earlier in tree
  order, so it paints under both the border strip and the plate fill: the
  overhang is covered, the crease reads as one continuous line, and the fold
  sits beneath the plate rather than on top of it. (A stacking context on the
  card -- isolation, opacity, a filter -- would order these more explicitly
  but would also make the card a backdrop root, which would leave
  .plate-inner's backdrop-filter blurring the card's own contents instead of
  the page behind it.)
*/
.plate-card::before {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  width: calc(var(--dogear) + var(--border-w));
  height: calc(var(--dogear) + var(--border-w));
  z-index: -1;
  /* Same gradient as the plate, shaded toward the crease so the fold reads as
     the reverse side of the sheet rather than another panel in the same plane. */
  background:
    linear-gradient(135deg, rgba(22, 22, 29, 0.45), rgba(22, 22, 29, 0) 70%),
    linear-gradient(155deg, var(--grad-1), var(--grad-3));
  clip-path: polygon(100% 0, 100% 100%, 0 100%);
}

/* ---------- ribbon flag ---------- */

.ribbon-outline {
  position: absolute;
  top: -0.8rem;
  left: -0.6rem;
  background: linear-gradient(100deg, var(--grad-3), var(--grad-1));
  padding: var(--border-w);
  clip-path: polygon(
    0 0, 100% 0,
    100% calc(100% - var(--dogear-ribbon)),
    calc(100% - var(--dogear-ribbon)) 100%,
    0 100%
  );
}

.ribbon-inner {
  clip-path: polygon(
    0 0, 100% 0,
    100% calc(100% - var(--dogear-ribbon-inner)),
    calc(100% - var(--dogear-ribbon-inner)) 100%,
    0 100%
  );
  background: linear-gradient(
    100deg,
    color-mix(in srgb, var(--grad-3) 80%, transparent),
    color-mix(in srgb, var(--grad-1) 80%, transparent)
  );
  backdrop-filter: blur(8px) saturate(1.4);
  -webkit-backdrop-filter: blur(8px) saturate(1.4);
  padding: 0.2rem 1.3rem 0.2rem 0.9rem;
  white-space: nowrap;
}

.ribbon-label {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 1rem;
  color: rgba(22, 22, 29, 0.75);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.4);
}

/* ---------- color themes ---------- */

.theme-mint {
  --grad-1: #c8ffe0;
  --grad-2: #8ff2d6;
  --grad-3: #7fd6ff;
  --glow: #5cf2c4;
}

.theme-peach {
  --grad-1: #ffe1ef;
  --grad-2: #ffc2a8;
  --grad-3: #fff29e;
  --glow: #ff9ecb;
}

.theme-violet {
  --grad-1: #d6ccff;
  --grad-2: #b79bff;
  --grad-3: #ff9ee6;
  --glow: #b98cff;
}

.theme-azure {
  --grad-1: #cdeaff;
  --grad-2: #9cc6ff;
  --grad-3: #c6a3ff;
  --glow: #6fb8ff;
}

.theme-sunset {
  --grad-1: #ffe0b3;
  --grad-2: #ff9a9e;
  --grad-3: #fecfef;
  --glow: #ff7e9e;
}

@media (max-width: 480px) {
  .plate-card { width: 100%; }
  .ribbon-row { flex-direction: column; }
}
