/* 123drg.com — the apex.
 *
 * Pattern 1: nginx-proxy reads these bytes off disk and sends them. No
 * container, no server-side code, no JavaScript, no webfont, no external
 * request of any kind. The whole page is two files and an icon.
 *
 * The apex CSP is `default-src 'self'` with no 'unsafe-inline' (tightened
 * during the 31 July 2026 audit, when it emerged that the only reason
 * 'unsafe-inline' was there at all was one inline <style> block). Two
 * consequences shape everything below:
 *
 *   1. There is no style="" attribute in index.html, so the per-card accent
 *      colours are CLASSES here rather than inline custom properties. That is
 *      the tidier approach anyway — the palette lives in one file.
 *   2. Icons are inline <svg> in the markup. An <svg> element is document
 *      content rather than a fetched resource, so CSP never sees it, and the
 *      page still makes exactly two requests.
 *
 * The look: a dark switchboard. Each card carries the accent colour of the
 * site it points at, so the grid reads as a map of the homelab rather than a
 * list of links — and the only thing that moves is what the pointer is on.
 */

:root {
  --bg: #050b16;
  --bg-glow: #08121f;
  --surface: #0b1826;
  --surface-2: #102132;
  --edge: #1a3048;
  --edge-lit: #27455f;

  --text: #dbe7f4;
  --text-2: #a2b6cc;
  --muted: #6b8299;

  --accent: #4aa3ff;   /* per-card classes below override this */

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  --radius: 14px;
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* Design system: sharp corners, enforced not remembered. See documentation/theme.md */
* { border-radius: 0 !important; }

/* Same page, daylight. Not an inverted dark theme — a lighter paper with the
   accents darkened enough to stay legible on it. */

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  background:
    radial-gradient(120% 80% at 50% -20%, var(--bg-glow) 0%, transparent 60%),
    var(--bg);
  color: var(--text);
  font: 16px/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: clamp(2.5rem, 7vh, 5rem) clamp(1rem, 4vw, 2rem) 2rem;
}

/* ------------------------------------------------------------------- hero */

.hero {
  width: min(60rem, 100%);
  text-align: center;
  margin-bottom: clamp(2rem, 5vh, 3.25rem);
}

.kicker {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 1rem;
}

h1 {
  margin: 0;
  font-size: clamp(2.6rem, 9vw, 4.5rem);
  font-weight: 700;
  letter-spacing: -0.04em;
  line-height: 1;
  /* The one flourish on the page: a slow sweep across the wordmark. It is
     decoration, so it is behind the motion query at the bottom of this file. */
  background: linear-gradient(100deg, var(--text) 20%, #35d0e8 50%, var(--text) 80%);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.tagline {
  margin: 1.1rem auto 0;
  max-width: 38rem;
  color: var(--text-2);
  font-size: 1.02rem;
}

/* ------------------------------------------------------------------- grid */

/* Explicit column counts rather than `auto-fit`, and the reason is a gap that
   only appears at certain widths. With `repeat(auto-fit, minmax(15.5rem, 1fr))`
   a 60rem container resolves to THREE columns; a card spanning 2 then leaves a
   single orphan column beside it, and the next spanning card wraps rather than
   filling it. The result is a hole in the grid at exactly the desktop width
   most people would see. Fixed tracks let a featured card span the full row by
   definition, at every breakpoint. */
.grid {
  width: min(60rem, 100%);
  display: grid;
  grid-template-columns: 1fr;          /* phones: one column, wide cards included */
  gap: 0.85rem;
}

@media (min-width: 46rem) {
  .grid { grid-template-columns: repeat(2, 1fr); }
  .card.wide { grid-column: 1 / -1; }  /* full width, whatever the count */
}

@media (min-width: 68rem) {
  .grid { grid-template-columns: repeat(3, 1fr); }
}

.card {
  position: relative;
  display: flex;
  gap: 0.9rem;
  padding: 1.15rem 1.2rem;
  border: 2px solid var(--edge);
  border-radius: var(--radius);
  background: linear-gradient(160deg, var(--surface) 0%, var(--surface-2) 100%);
  color: inherit;
  text-decoration: none;
  overflow: hidden;
  transition:
    transform 320ms var(--ease),
    border-color 320ms ease,
    box-shadow 320ms ease;
}

/* A wash of the card's own accent, revealed on hover. Painted as a pseudo
   element rather than a background swap so it can cross-fade independently of
   the base gradient. */
.card::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(80% 120% at 0% 0%, var(--accent) 0%, transparent 70%);
  opacity: 0;
  transition: opacity 320ms ease;
  pointer-events: none;
}

.card:hover,
.card:focus-visible {
  transform: translateY(-3px);
  border-color: var(--accent);
  box-shadow: 0 14px 34px -18px rgba(0, 0, 0, 0.85);
  outline: none;
}
.card:hover::before,
.card:focus-visible::before { opacity: 0.07; }

/* Keyboard users get a real focus ring, not just the hover treatment. */
.card:focus-visible { box-shadow: 0 0 0 2px var(--accent), 0 14px 34px -18px rgba(0, 0, 0, 0.85); }

.icon {
  flex: none;
  width: 2.35rem;
  height: 2.35rem;
  display: grid;
  place-items: center;
  border-radius: 10px;
  border: 2px solid var(--edge);
  color: var(--accent);
  background: var(--bg);
  transition: border-color 320ms ease, transform 320ms var(--ease);
}
.icon svg { width: 1.2rem; height: 1.2rem; display: block; }

.card:hover .icon,
.card:focus-visible .icon { border-color: var(--accent); transform: scale(1.06); }

.body { display: flex; flex-direction: column; gap: 0.15rem; min-width: 0; }

.name { font-weight: 640; font-size: 1.02rem; letter-spacing: -0.01em; }

.host {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--accent);
  letter-spacing: 0.02em;
}

.desc {
  margin-top: 0.3rem;
  color: var(--text-2);
  font-size: 0.88rem;
  line-height: 1.5;
}

/* "private" badge on the two gated services. Saying so up front is a courtesy,
   not a leak: it turns an unexplained login wall into an expected one, and it
   discloses nothing an attacker would not learn by loading the page. */
.lock {
  display: inline-block;
  margin-left: 0.45rem;
  padding: 0.08em 0.45em;
  vertical-align: 0.12em;
  border: 2px solid var(--accent);
  border-radius: 999px;
  font-family: var(--mono);
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0.85;
}

/* --------------------------------------------- per-site accents
   Each card wears the colour of the site it opens, so the grid doubles as a
   legend for the rest of the domain. */

.about    { --accent: #5cb0ff; }   /* about is Steel */   /* the docs site's green */
.resume   { --accent: #4aa3ff; }   /* resume is Signal */   /* the résumé's ops-centre cyan */
.jellyfin { --accent: #4aa3ff; }   /* Jellyfin's own purple */
.rolodex  { --accent: #f2c53d; }
.bocce    { --accent: #ff9426; }
.strike   { --accent: #ff9426; }
.advisor  { --accent: #2fe3e0; }   /* c.123drg.com is Phosphor */
.comments { --accent: #4ee88a; }   /* b.123drg.com is Phosphor */
.design   { --accent: #9fb3ca; }   /* the style guide itself — deliberately neutral */


/* ----------------------------------------------------------------- footer */

footer {
  width: min(60rem, 100%);
  margin-top: clamp(2rem, 5vh, 3rem);
  padding-top: 1.4rem;
  border-top: 1px solid var(--edge);
  color: var(--muted);
  font-size: 0.85rem;
}
footer p { margin: 0; }
footer code {
  font-family: var(--mono);
  font-size: 0.92em;
  padding: 0.05em 0.35em;
  border: 2px solid var(--edge);
  border-radius: 5px;
  color: var(--text-2);
}
footer a {
  color: var(--text-2);
  text-decoration: none;
  border-bottom: 1px solid var(--edge-lit);
  transition: color 200ms ease, border-color 200ms ease;
}
footer a:hover { color: #35d0e8; border-bottom-color: #35d0e8; }

/* ------------------------------------------------------------------ motion
   Everything above is legible and complete without a single animation. What is
   gated here is decoration only: the wordmark sweep and the card lift. Someone
   who has told their OS that movement makes them ill still gets the whole page,
   with instant state changes instead of eased ones. */

@media (prefers-reduced-motion: no-preference) {
  h1 { animation: sweep 9s ease-in-out infinite; }
}

@keyframes sweep {
  0%, 100% { background-position: 120% 50%; }
  50%      { background-position: -20% 50%; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .card:hover, .card:focus-visible { transform: none; }
  h1 { background-position: 50% 50%; }
}

/* ------------------------------------------------------------------- print
   Nobody prints a link page, but a dark rectangle is a rude thing to hand a
   printer. Cheap to make it behave. */

@media print {
  body { background: #fff; color: #000; padding: 0; }
  h1 { color: #000; background: none; -webkit-text-fill-color: #000; animation: none; }
  .card { border-color: #bbb; background: #fff; break-inside: avoid; }
  .card::before, .icon { display: none; }
  .host::after { content: ""; }
}
