/* Frontline theme — shared foundation for arkavo.github.io and the project
   sites under it. Palette, typography, controls, nav, canvas layer and the
   section/scroll-cue scaffolding. Page-specific layout lives elsewhere. */

/* Frontline theme — palette shared with the Ghostty/Starship terminal setup. */

:root {
  --bg: #f0f0ed;
  --fg: #191d1e;
  --muted: #565d60;
  --border: #cdd0cd;
  --surface: rgba(248, 248, 246, 0.93);
  --accent: #d9c400;
  --accent-text: #8f7d00;
  --on-accent: #1c2021;
  --grid: rgba(28, 32, 33, 0.06);

  /* Canvas ink. Dark strokes on a near-white ground carry far less weight than
     light strokes on near-black, so light mode scales every map alpha up and
     swaps the index contours to the darker gold. */
  --contour-index: #8f7d00;
  --map-boost: 1.7;

  /* IBM Plex Sans and Mono are one designed superfamily — shared skeletons and
     terminals, so the prose, the mono labels and the map annotations all read
     as the same hand. Industrial by origin, which suits the rest of this. */
  --font-ui: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* Maths setting. IBM Plex Mono has no glyph for subscript j or n, capital
     Sigma, or any Greek, so an equation set in it silently falls back mid-string
     and loses monospace alignment. DejaVu Sans Mono carries the lot — verified
     against its cmap — so equations and physical quantities use it instead. */
  --font-math: "DejaVu Sans Mono", "IBM Plex Mono", ui-monospace, Menlo, monospace;

  /* single source of truth for the sticky nav — the hero height and every
     anchor offset are derived from it */
  --nav-h: 3.5rem;

  /* Reading measure for body copy. Set to exactly the section's inner width
     (56rem less its 1.5rem side padding) so the About column and the project
     cards resolve to the same left and right edges. */
  --measure: 53rem;
}

/* Dark applies when the system asks for it AND the visitor has not explicitly
   chosen light, or whenever they have explicitly chosen dark. The palette is
   spelled out twice because CSS has no way to share a block between two
   selectors — keep the pair in sync. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #16191a;
    --fg: #e2e5e6;
    --muted: #848c8f;
    --border: #3e4446;
    --surface: rgba(24, 28, 29, 0.93);
    --accent: #f5f551;
    --accent-text: #f5f551;
    --on-accent: #16191a;
    --grid: rgba(226, 229, 230, 0.05);
    --contour-index: #f5f551;
    --map-boost: 1;
  }
}

:root[data-theme="dark"] {
  --bg: #16191a;
  --fg: #e2e5e6;
  --muted: #848c8f;
  --border: #3e4446;
  --surface: rgba(24, 28, 29, 0.93);
  --accent: #f5f551;
  --accent-text: #f5f551;
  --on-accent: #16191a;
  --grid: rgba(226, 229, 230, 0.05);
  --contour-index: #f5f551;
  --map-boost: 1;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

html, body {
  min-height: 100%;
  margin: 0;
}

body {
  font-family: var(--font-ui);
  color: var(--fg);
  background-color: var(--bg);
}

/* Corner artwork sits bottom-right and stays put while the page scrolls.
   Its own background matches --bg exactly, so the empty area blends away. */
#bg-canvas {
  position: fixed;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* The native pointer is hidden only once the custom one is confirmed live,
   so a browser that never fires mousemove is left with a usable cursor.

   !important is deliberate. `body.cursor-on *` scores (0,1,1), which merely
   ties with a component rule like `.pub summary { cursor: pointer }` — and
   that lives in a later stylesheet, so it won. The result was the native arrow
   reappearing over those rows while the bracket was still drawn: two cursors.
   This is a global mode, so it has to outrank every component rule, including
   ones added later. */
body.cursor-on,
body.cursor-on *,
body.cursor-on *::before,
body.cursor-on *::after {
  cursor: none !important;
}

#cursor {
  position: fixed;
  top: 0;
  left: 0;
  width: 32px;
  height: 32px;
  z-index: 60;
  color: var(--accent);
  pointer-events: none;
  opacity: 0;
  will-change: transform;
  transition: opacity 0.18s ease;
}

#cursor.on {
  opacity: 1;
}

/* Backing stroke in the page colour, so the accent brackets stay legible
   even when they pass over an accent-filled button. */
.cursor-halo {
  stroke: var(--bg);
  opacity: 0.9;
}

main, header, footer {
  position: relative;
  z-index: 1;
}

h1 {
  margin: 0;
  font-size: clamp(2rem, 7vw, 3.5rem);
  font-weight: 700;
  letter-spacing: 0.02em;
}

h2 {
  margin: 0 0 1.5rem;
  font-size: clamp(1.4rem, 4vw, 1.9rem);
  font-weight: 700;
  letter-spacing: -0.01em;
}

h3 {
  margin: 0;
  font-size: 1.1rem;
  font-weight: 700;
}

p {
  margin: 0;
  color: var(--muted);
  line-height: 1.6;
}

a {
  color: var(--accent-text);
}

.button-link {
  display: inline-block;
  padding: 0.6rem 1.4rem;
  border: 1px solid var(--accent);
  border-radius: 2px;
  color: var(--accent-text);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.9rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  transition: background 0.15s ease, color 0.15s ease;
}

.button-link:hover {
  background: var(--accent);
  color: var(--on-accent);
}

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

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

.nav {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--nav-h);
  padding: 0 1.5rem;
  border-bottom: 1px solid var(--border);
  backdrop-filter: blur(10px);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
}

/* The theme swap is a circle wiped over the old page from the button. Both
   snapshots are held still and the new one is stacked on top, so the only
   motion is the clip-path the toggle animates. */
::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}

::view-transition-old(root) {
  z-index: 0;
}

::view-transition-new(root) {
  z-index: 1;
}

/* Shows the icon for the theme you would switch TO, not the current one. */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 2px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.theme-toggle:hover,
.theme-toggle:focus-visible {
  border-color: var(--accent);
  color: var(--accent-text);
  background: color-mix(in srgb, var(--accent) 9%, transparent);
}

.theme-toggle .icon {
  width: 1rem;
  height: 1rem;
}

.theme-toggle .icon-sun { display: none; }
.theme-toggle .icon-moon { display: block; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-sun { display: block; }
  :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
}

:root[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }

.nav-links {
  display: flex;
  gap: 1.5rem;
}

.nav-links a {
  display: flex;
  align-items: center;
  /* fills the nav height so the tap target is the full bar, not the glyphs */
  min-height: var(--nav-h);
  color: var(--muted);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 0.85rem;
  font-weight: 600;
  transition: color 0.15s ease;
}

.nav-links a:hover {
  color: var(--accent-text);
}

/* A plain anchor, so the smooth scroll and the section's scroll-margin do the
   work without any script. In sections it sits in the flow and closes them
   out; in the hero it is pinned to the empty lower third. */
.scroll-cue {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: -moz-fit-content;
  width: fit-content;
  /* Balanced against the space below it: this section's trimmed bottom padding
     (2rem) plus the next section's 4rem top padding also comes to 6rem, so the
     cue sits centred in the trough between sections rather than hugging one. */
  margin: 6rem auto 0;
  padding: 0.5rem 1.5rem;
  color: var(--muted);
  transition: color 0.15s ease;
}

/* Dropped to the foot of the section's leftover space. The padding guarantees
   clear air above the chevrons even when the content already fills the screen
   and `auto` resolves to nothing. */
.section > .scroll-cue {
  margin-top: auto;
  padding-top: 4rem;
}

.scroll-cue:hover,
.scroll-cue:focus-visible {
  color: var(--accent-text);
  outline: none;
}

.scroll-cue .icon {
  width: 1.5rem;
  height: 1.5rem;
  /* overlapped so the three read as one arrow, not a stack */
  margin-top: -0.7rem;
  opacity: 0.3;
  animation: cue 1.9s ease-in-out infinite;
}

.scroll-cue .icon:first-child {
  margin-top: 0;
}

/* staggered down the stack, so the pulse travels downward */
.scroll-cue .icon:nth-child(2) { animation-delay: 0.16s; }
.scroll-cue .icon:nth-child(3) { animation-delay: 0.32s; }

@keyframes cue {
  0%, 70%, 100% { opacity: 0.22; }
  35% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-cue .icon {
    animation: none;
    opacity: 0.55;
  }
}

/* Every section stands at least a screen tall, so each one arrives on its own
   rather than several crowding into one view. Flex column so the cue can be
   pushed to the foot of whatever space is left over. */
.section {
  display: flex;
  flex-direction: column;
  min-height: calc(100vh - var(--nav-h));
  max-width: 56rem;
  margin: 0 auto;
  padding: 4rem 1.5rem;
  scroll-margin-top: calc(var(--nav-h) + 1rem);
}

/* A section that ends on a cue gives back some of its bottom padding — the
   cue's own spacing supplies the separation instead. */
.section:has(> .scroll-cue) {
  padding-bottom: 2rem;
}

.tag-list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 1.5rem 0 0;
  padding: 0;
}

.tag-list li {
  padding: 0.35rem 0.9rem;
  border: 1px solid var(--border);
  border-radius: 2px;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.8rem;
}

.icon {
  width: 1.25rem;
  height: 1.25rem;
  flex-shrink: 0;
}

/* the contact section already ends on 4rem of padding — no need to stack more */
.footer {
  text-align: center;
  padding: 0 1.5rem 3rem;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: 0.8rem;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .hello-cursor { animation: none; }
}

/* ---- small screens ---- */

@media (max-width: 640px) {
  .section {
    padding: 3rem 1.25rem;
  }

  .nav-links {
    gap: 1.1rem;
  }
}

/* Current page in a multi-page nav — the item stays present rather than being
   dropped from the list, so the set of links never shifts between pages. */
.nav-links a[aria-current="page"] {
  color: var(--accent-text);
}


/* ---- maths face ----
   Self-hosted so no subsetting CDN can drop the rarer glyphs. Only rules under
   .math / .eq-* reference it, so a page without equations never fetches it. */
@font-face {
  font-family: "DejaVu Sans Mono";
  src: url("/fonts/DejaVuSansMono.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "DejaVu Sans Mono";
  src: url("/fonts/DejaVuSansMono-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* inline physical quantities */
.math {
  font-family: var(--font-math);
  font-size: 0.92em;
  color: var(--accent-text);
}

/* Browser defaults for sub/sup add leading and shove the line box around.
   Zeroing line-height and positioning them manually keeps prose lines even.
   No font-family here on purpose — digits and letters exist in the body face,
   so a paragraph never has to pull down the maths font. */
sub, sup {
  font-size: 0.72em;
  line-height: 0;
  position: relative;
  vertical-align: baseline;
}

sub { bottom: -0.25em; }
sup { top: -0.42em; }

/* Return-to-Base control. Uses the site's own mountain mark — the same glyph as
   its favicon — so it reads as a logo-home link rather than a labelled item.
   Boxed to mirror the theme toggle at the opposite end of the bar. */
/* `.nav-links a` sets min-height to the full bar for touch targets, which
   stretched this into a tall rectangle — scoped higher to keep it square. */
.nav-links .nav-home {
  min-height: 0;
  height: 2rem;
}

.nav-home {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  margin-left: 0.35rem;
  border: 1px solid var(--border);
  border-radius: 2px;
  color: var(--muted);
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.nav-home:hover,
.nav-home:focus-visible {
  border-color: var(--accent);
  color: var(--accent-text);
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  outline: none;
}

.nav-home .icon {
  width: 1rem;
  height: 1rem;
}

/* the nav row centres its items, so the boxed link needs no baseline nudge */
.nav-links {
  align-items: center;
}
