/*
 * edupema.dev — single stylesheet, no build step.
 *
 * Layering, in order: tokens, reset, then one block per part of the page. Colour lives
 * only in the token block at the top; nothing below it writes a literal colour, so a
 * repaint is an edit in one place.
 *
 * Themes have three states. An explicit choice stamps data-theme on <html>; the
 * default ("system") stamps nothing and lets prefers-color-scheme decide. So the
 * dark values are declared twice — once behind the media query, guarded so an
 * explicit "light" wins over a dark OS, and once behind the attribute, so the
 * toggle wins over a light OS.
 */

/* ── Tokens ─────────────────────────────────────────────────────────────── */

:root {
  color-scheme: light;

  /* Cool neutral ground, so the blue accent sits on it rather than fighting it. */
  --bg: #f7f8fa;
  --bg-sunk: #eef1f5;
  --surface: #ffffff;
  --surface-hover: #f4f7fb;

  --text: #16191d;
  --text-soft: #4b5563;
  --text-muted: #646e7a;

  --border: #e0e5ec;
  --border-strong: #cbd3dd;

  /* One accent. Deep enough to label a white button and to be read as text on the
     ground: both were measured before the value was fixed, and the muted grey above
     was darkened because the card's host line failed that same test.

     Declared as channels with the colour derived from them, because the accent is also
     needed at partial alpha — the backdrop wash below, the card's own ground. Written as
     `rgb(26 95 180 / 7%)` those are hand-typed copies that repainting `--accent` silently
     leaves behind, which is exactly what had happened here: the comment under `--backdrop`
     claimed it followed the accent while holding its own frozen copy of it. */
  --accent-rgb: 26 95 180;
  --accent: rgb(var(--accent-rgb));
  --accent-hover: #164e96;
  --accent-soft: #eaf2fd;
  --accent-border: #bcd7f5;
  --on-accent: #ffffff;

  --focus: var(--accent);

  --shadow-card: 0 1px 2px rgb(22 25 29 / 6%), 0 8px 24px -12px rgb(22 25 29 / 18%);
  --shadow-card-hover: 0 2px 4px rgb(22 25 29 / 8%), 0 16px 32px -12px rgb(22 25 29 / 22%);

  /* A wash behind the top of the page, painted with the accent so it follows the theme. */
  --backdrop: radial-gradient(
    ellipse 110% 75% at 50% -12%,
    rgb(var(--accent-rgb) / 11%),
    transparent 72%
  );

  /* Scale. Fluid where the jump between phone and desktop is big enough to matter. */
  --step--1: 0.875rem;
  --step-0: 1rem;
  --step-1: clamp(1.125rem, 1.05rem + 0.35vw, 1.3rem);
  --step-3: clamp(2rem, 1.55rem + 2.2vw, 3.25rem);

  --gap: 1rem;
  --measure: 62ch;
  --page: 68rem;
  --radius: 12px;
  --radius-sm: 8px;

  /* Touch floor: a minimum for an interactive target, never a size. */
  --tap: 44px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    color-scheme: dark;

    --bg: #0f1216;
    --bg-sunk: #0a0c0f;
    --surface: #171b21;
    --surface-hover: #1e242c;

    --text: #eef2f7;
    --text-soft: #b4bdc8;
    --text-muted: #7f8a97;

    --border: #262c34;
    --border-strong: #38404a;

    --accent-rgb: 108 176 255;
    --accent: rgb(var(--accent-rgb));
    --accent-hover: #8dc3ff;
    --accent-soft: #101f30;
    --accent-border: #23405e;
    --on-accent: #07131f;

    --focus: var(--accent-hover);

    --shadow-card: 0 1px 2px rgb(0 0 0 / 40%), 0 8px 24px -12px rgb(0 0 0 / 70%);
    --shadow-card-hover: 0 2px 4px rgb(0 0 0 / 45%), 0 16px 32px -12px rgb(0 0 0 / 80%);

    --backdrop: radial-gradient(
      ellipse 110% 75% at 50% -12%,
      rgb(var(--accent-rgb) / 17%),
      transparent 72%
    );
  }
}

:root[data-theme='dark'] {
  color-scheme: dark;

  --bg: #0f1216;
  --bg-sunk: #0a0c0f;
  --surface: #171b21;
  --surface-hover: #1e242c;

  --text: #eef2f7;
  --text-soft: #b4bdc8;
  --text-muted: #7f8a97;

  --border: #262c34;
  --border-strong: #38404a;

  --accent-rgb: 108 176 255;
  --accent: rgb(var(--accent-rgb));
  --accent-hover: #8dc3ff;
  --accent-soft: #101f30;
  --accent-border: #23405e;
  --on-accent: #07131f;

  --focus: var(--accent-hover);

  --shadow-card: 0 1px 2px rgb(0 0 0 / 40%), 0 8px 24px -12px rgb(0 0 0 / 70%);
  --shadow-card-hover: 0 2px 4px rgb(0 0 0 / 45%), 0 16px 32px -12px rgb(0 0 0 / 80%);

  --backdrop: radial-gradient(
    ellipse 110% 75% at 50% -12%,
    rgb(var(--accent-rgb) / 17%),
    transparent 72%
  );
}

/* ── Reset ──────────────────────────────────────────────────────────────── */

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

* {
  margin: 0;
}

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

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  background-color: var(--bg);
  background-image: var(--backdrop);
  background-repeat: no-repeat;
  color: var(--text);
  font-family:
    ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  font-size: var(--step-0);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img,
svg {
  display: block;
  max-width: 100%;
}

a {
  color: inherit;
}

:where(a, button):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--surface);
  color: var(--text);
  padding: 0.75rem 1rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  z-index: 10;
}

.skip-link:focus {
  left: 1rem;
  top: 1rem;
}

/* ── Header ─────────────────────────────────────────────────────────────── */

.site-header {
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding: 1.25rem var(--gap);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap);
}

.wordmark {
  display: inline-flex;
  align-items: center;
  font-weight: 620;
  letter-spacing: -0.015em;
  text-decoration: none;
}

/* The favicon, at header size. The spacing is this element's margin, not a `gap` on the flex
   container: the bare text node "edupema" and the `.dev` span are two flex items, so a gap
   would push them apart and read as "edupema .dev". */
.wordmark__mark {
  width: 26px;
  height: 26px;
  margin-right: 0.55rem;
  flex: none;
}

.wordmark__tld {
  color: var(--text-muted);
  font-weight: 480;
}

.theme-toggle {
  display: inline-grid;
  place-items: center;
  min-width: var(--tap);
  min-height: var(--tap);
  padding: 0;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text-soft);
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease, background-color 120ms ease;
}

.theme-toggle:hover {
  color: var(--text);
  background: var(--surface-hover);
  border-color: var(--border-strong);
}

/* `hidden` alone loses to the `display` above, and the button ships hidden — see index.html. */
.theme-toggle[hidden] {
  display: none;
}

/* Which half of the icon is showing is the theme's job, not JavaScript's: the
   toggle only writes data-theme, and both the attribute and the media query
   reach these rules. */
.theme-toggle__moon {
  display: none;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .theme-toggle__sun {
    display: none;
  }

  :root:not([data-theme='light']) .theme-toggle__moon {
    display: block;
  }
}

:root[data-theme='dark'] .theme-toggle__sun {
  display: none;
}

:root[data-theme='dark'] .theme-toggle__moon {
  display: block;
}

/* ── Layout ─────────────────────────────────────────────────────────────── */

main {
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding: 0 var(--gap) 4rem;
  /* Takes the leftover height so the footer sits at the bottom of a short page. */
  flex: 1;
}

/* ── Hero ───────────────────────────────────────────────────────────────── */

.hero {
  padding-block: clamp(2.5rem, 1.5rem + 5vw, 5rem) clamp(2rem, 1.5rem + 2vw, 3rem);
}

.hero__eyebrow {
  font-size: var(--step--1);
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--accent);
  font-weight: 620;
}

.hero__title {
  margin-top: 0.75rem;
  font-size: var(--step-3);
  line-height: 1.08;
  letter-spacing: -0.03em;
  font-weight: 680;
  text-wrap: balance;
}

.hero__lead {
  margin-top: 1rem;
  max-width: var(--measure);
  font-size: var(--step-1);
  color: var(--text-soft);
}

/* The 404 page's only control. */
.hero__actions {
  margin-top: 2rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--tap);
  padding: 0.5rem 1.25rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-weight: 560;
  text-decoration: none;
  transition: background-color 120ms ease, border-color 120ms ease, transform 120ms ease;
}

.button:hover {
  background: var(--surface-hover);
  border-color: var(--text-muted);
}

.button--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
}

.button--primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.button:active {
  transform: translateY(1px);
}

/* ── Cards ──────────────────────────────────────────────────────────────── */

/* `auto-fill` with a capped track, not `auto-fit` with `1fr`: two cards on a wide screen
   would otherwise stretch to half the viewport each and read as mostly empty. Capped, they
   keep a card's proportions and simply sit left; a third one joins the row instead of
   reflowing the other two. The `min(100%, …)` floor is what keeps a narrow phone from
   demanding a track wider than the screen. */
.cards {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 22rem));
}

/* A card is a poster, and a poster is dark in both themes.
   That is deliberate, and it is the one place this stylesheet does not follow the colour
   scheme. The art behind the text is a photograph or a glyph whose tones nobody controls,
   so the only combination that stays legible over all of it is light type on a darkened
   ground — inverting that per theme would mean re-checking every image twice. The ink
   tokens below are therefore fixed, and they are declared here rather than in the theme
   block precisely because no theme gets to move them.

   The stack, back to front: the art, the scrim that darkens it, then the text. Both layers
   sit at a negative z-index against the card's own isolation, so they never escape it and
   nothing inside needs a z-index of its own. */
.card {
  /* Channels, for the same reason the accent is: the badge and the scrim need these at
     partial alpha, and typing `rgb(244 247 251 / 12%)` there is a copy that repainting the
     ink leaves behind — silently, since the badge would simply keep the old colour. */
  --card-ink-rgb: 244 247 251;
  --card-ink: rgb(var(--card-ink-rgb));
  --card-ink-soft: #b9c4d2;
  --card-tint: #101823;
  --card-lift: #16233a;
  --card-scrim-rgb: 11 16 24;

  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 14rem;
  height: 100%;
  padding: 1.5rem;
  /* The card's own ground, and the fallback for one that has no art yet: a card is drawn
     before its image exists, and an empty rectangle would look broken rather than pending.
     An `.card__art` covers this completely, so it costs a card that has one nothing. */
  background:
    radial-gradient(ellipse 120% 90% at 20% 0%, rgb(var(--accent-rgb) / 55%), transparent 60%),
    linear-gradient(160deg, var(--card-lift) 0%, var(--card-tint) 70%);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  color: var(--card-ink);
  text-decoration: none;
  transition: transform 140ms ease, box-shadow 140ms ease, border-color 140ms ease;
}

.card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-card-hover);
  border-color: var(--accent-border);
}

.card__art {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Off-centre and oversized: the art reads as a crop of something bigger rather than as a
     logo pasted in the middle, and the text corner stays the emptiest part of it. */
  /* Fully opaque, and darkened by the scrim alone. Fading the art instead would blend it
     with the card's own blue ground and tint every image towards it. */
  object-position: center 38%;
  scale: 1.06;
  transition: scale 400ms ease;
}

.card:hover .card__art {
  scale: 1.14;
}

/* The scrim, and the only thing standing between the text and an unreadable image. It is
   strongest where the text sits and never fully clears at the top, so a bright frame cannot
   wash the card out. Weaken the art before weakening this. */
.card::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    180deg,
    rgb(var(--card-scrim-rgb) / 45%) 0%,
    rgb(var(--card-scrim-rgb) / 72%) 55%,
    rgb(var(--card-scrim-rgb) / 93%) 100%
  );
}

/* Sits with the title at the bottom, not pinned to the top: pushed up there by an `auto`
   margin it crossed the middle of the art, which is where the art is worth looking at. */
.card__badge {
  align-self: flex-start;
  margin-bottom: 0.7rem;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 620;
  color: var(--card-ink);
  background: rgb(var(--card-ink-rgb) / 12%);
  border: 1px solid rgb(var(--card-ink-rgb) / 22%);
  border-radius: 999px;
  padding: 0.2rem 0.7rem;
  backdrop-filter: blur(4px);
}

.card__title {
  font-size: var(--step-1);
  line-height: 1.25;
  letter-spacing: -0.015em;
  font-weight: 640;
}

/* ── Footer ─────────────────────────────────────────────────────────────── */

.site-footer {
  width: 100%;
  max-width: var(--page);
  margin-inline: auto;
  padding: 1.5rem var(--gap) 2.5rem;
  border-top: 1px solid var(--border);
  color: var(--text-muted);
  font-size: var(--step--1);
}

.social {
  list-style: none;
  padding: 0;
  display: flex;
  gap: 0.25rem;
  /* Pulls the first icon's tap area back so the glyph, not its padding, lines up with the
     page's left margin. */
  margin-left: calc(var(--tap) / -2 + 0.6rem);
}

.social__link {
  display: inline-grid;
  place-items: center;
  min-width: var(--tap);
  min-height: var(--tap);
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  transition: color 120ms ease, background-color 120ms ease;
}

.social__link:hover {
  color: var(--accent);
  background: var(--surface-hover);
}
