:root {
  /* Institutional palette: UFRGS blue (links, primary) + INF-UFRGS red (accents) */
  --ufrgs-blue: #003366;
  --ufrgs-blue-dark: #002347;
  --ufrgs-blue-light: #004fce;     /* link color — matches original site (Hestia accent) */
  --ufrgs-blue-lighter: #3370e6;   /* lighter shade of #004fce for link hover */
  --inf-red: #ED1C24;
  --inf-red-dark: #B81017;

  --bg: #ffffff;
  --bg-soft: #f5f5f6;
  --bg-page: #f3f4f5;            /* page-level neutral gray (body background) */
  --surface: #ffffff;             /* raised surfaces: header, container, chips */
  --border: #e5e5e7;
  --neutral-divider: #c4c5c8;    /* subtle dividers (e.g. nav hover underline) */
  --text: #1a1a1c;
  --text-muted: #6c6e72;
  --link: var(--ufrgs-blue-light);
  --link-hover: var(--ufrgs-blue-lighter);

  /* Tinted overlays derived from brand colors (for badges, current-page bg) */
  --tint-blue-soft: rgba(0, 51, 102, 0.08);   /* derived from --ufrgs-blue */
  --tint-blue-border: rgba(0, 51, 102, 0.2);
  --tint-red-soft: rgba(237, 28, 36, 0.05);   /* derived from --inf-red */
  /* Status / category accent palette (used by tags). Discreet tints. */
  --accent-green:        #2a7a3a;
  --tint-green-soft:     rgba(42, 122, 58, 0.08);
  --tint-green-border:   rgba(42, 122, 58, 0.25);
  --accent-orange:       #b86e1e;
  --tint-orange-soft:    rgba(184, 110, 30, 0.08);
  --tint-orange-border:  rgba(184, 110, 30, 0.25);

  --radius: 6px;
  --radius-pill: 999px;
  --shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 1px 3px rgba(16, 24, 40, 0.06);
  /* Sticky-header drop shadow — pulled out as a variable so the dark
     theme can swap it for a stronger version (rgba(0,0,0) at low alpha
     is invisible against a dark surface). */
  --header-shadow:
    0 1px 10px -6px rgba(0, 0, 0, 0.42),
    0 1px 10px  0   rgba(0, 0, 0, 0.12),
    0 4px 5px  -2px rgba(0, 0, 0, 0.10);

  --font-sans: "Source Sans 3", "Source Sans Pro", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-body: var(--font-sans);

  /* Harmonic type scale (~1.2 ratio for headings, anchored at 19px body) */
  --text-xs:   0.875rem;   /* 14px — tags, micro labels */
  --text-sm:   1.0625rem;  /* 17px — meta, captions, nav, secondary UI */
  --text-base: 1.1875rem;  /* 19px — body */
  --text-md:   1.3125rem;  /* 21px — lead */
  --text-lg:   1.4375rem;  /* 23px — h3 */
  --text-xl:   1.75rem;    /* 28px — h2 */
  --text-2xl:  2.0625rem;  /* 33px — h1 default */
  --text-3xl:  2.5rem;     /* 40px — hero h1 */

  --container: 1080px;
}

/* ========================================================================
   Dark mode
   ------------------------------------------------------------------------
   Two activation paths:
     1. The OS-level `prefers-color-scheme: dark` media query — the default
        for users who haven't expressed an explicit preference on this site.
     2. An explicit `data-theme="dark"` attribute on <html> — applied by a
        bootstrap script in <head> when the user has saved a manual choice
        in localStorage. This wins over the OS preference.
   The light theme is the default in `:root` above; dark mode just swaps
   the values of the same custom properties so every downstream rule keeps
   working without modification.
   ======================================================================== */

/* Shared dark-mode token block — copy in both selectors below to avoid
   relying on CSS-variable-of-variables (which gets harder to debug). */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #1a1a1c;
    --bg-soft: #2a2c30;
    --bg-page: #131316;
    --surface: #222426;
    --border: #3a3c40;
    --neutral-divider: #4a4c50;
    --text: #e8e8ea;
    --text-muted: #a4a6ab;
    /* Lifted brand-blues — the originals (#003366 / #002347) are too dark
       to be visible against the dark surfaces; these are still recognizably
       UFRGS but with enough lightness to work as both fg and bg. */
    --ufrgs-blue: #5a8fcf;
    --ufrgs-blue-dark: #3a6da8;
    --ufrgs-blue-light: #7aaaff;
    --ufrgs-blue-lighter: #a4c4ff;
    /* Accents — slightly brighter so tags read on dark surfaces. */
    --accent-green: #6bc082;
    --accent-orange: #d99e60;
    /* Tints — built from the lifted accents instead of the originals. */
    --tint-blue-soft:    rgba(122, 170, 255, 0.13);
    --tint-blue-border:  rgba(122, 170, 255, 0.35);
    --tint-red-soft:     rgba(237, 28, 36, 0.12);
    --tint-green-soft:   rgba(107, 192, 130, 0.13);
    --tint-green-border: rgba(107, 192, 130, 0.35);
    --tint-orange-soft:  rgba(217, 158, 96, 0.13);
    --tint-orange-border:rgba(217, 158, 96, 0.35);
    /* Shadows go deeper / more diffuse on dark — pure black at low alpha. */
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.3);
    /* Header shadow — pushed harder than light mode (alpha ~0.6/0.4/0.3
       vs light's 0.42/0.12/0.10) so the dark band reads as a visible
       drop against the dark surface instead of disappearing. */
    --header-shadow:
      0 1px 10px -6px rgba(0, 0, 0, 0.7),
      0 1px 10px  0   rgba(0, 0, 0, 0.45),
      0 4px 8px  -2px rgba(0, 0, 0, 0.4);
  }
}
:root[data-theme="dark"] {
  --bg: #1a1a1c;
  --bg-soft: #2a2c30;
  --bg-page: #131316;
  --surface: #222426;
  --border: #3a3c40;
  --neutral-divider: #4a4c50;
  --text: #e8e8ea;
  --text-muted: #a4a6ab;
  --ufrgs-blue: #5a8fcf;
  --ufrgs-blue-dark: #3a6da8;
  --ufrgs-blue-light: #7aaaff;
  --ufrgs-blue-lighter: #a4c4ff;
  --accent-green: #6bc082;
  --accent-orange: #d99e60;
  --tint-blue-soft:    rgba(122, 170, 255, 0.13);
  --tint-blue-border:  rgba(122, 170, 255, 0.35);
  --tint-red-soft:     rgba(237, 28, 36, 0.12);
  --tint-green-soft:   rgba(107, 192, 130, 0.13);
  --tint-green-border: rgba(107, 192, 130, 0.35);
  --tint-orange-soft:  rgba(217, 158, 96, 0.13);
  --tint-orange-border:rgba(217, 158, 96, 0.35);
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.3);
  --header-shadow:
    0 1px 10px -6px rgba(0, 0, 0, 0.7),
    0 1px 10px  0   rgba(0, 0, 0, 0.45),
    0 4px 8px  -2px rgba(0, 0, 0, 0.4);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--bg-page);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; display: block; }
a { color: var(--link); text-decoration: none; }

/* Center-out underline reveal on hover for blue text links. The underline
   uses currentColor so it always matches the link color, and grows from
   the center toward both edges. Speed matches the nav-menu underline
   (0.12s ease-out). Honors prefers-reduced-motion. */
a {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 50% calc(100% - 0.12em);
  background-size: 0 0.08em;
}
a:hover {
  background-size: 100% 0.08em;
  transition: background-size 0.12s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  a:hover { transition: none; }
}
/* Opt-out for links that have their own hover treatment or are icon-only
   (no text to underline). */
.site-header a,
.year-jump a,
.pub-link-icon,
.pub-bibtex-toggle,
.pub-bibtex-copy {
  background-image: none;
}

h1, h2, h3, h4 {
  font-family: var(--font-sans);
  color: var(--text);
  line-height: 1.25;
  margin: 0 0 0.5em;
  letter-spacing: -0.01em;
}
h1 { font-size: var(--text-2xl); font-weight: 800; letter-spacing: -0.015em; }
h2 { font-size: var(--text-xl); font-weight: 600; margin-top: 2rem; }
h3 { font-size: var(--text-lg); font-weight: 500; }
h4 { font-size: var(--text-md); font-weight: 600; }

p { margin: 0 0 1em; }
ul, ol { padding-left: 1.25rem; }
code { font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 0.9em; }

.container {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 1.25rem;
}

/* Main content "card" — Hestia-style raised panel over gray page bg */
main.container {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 2rem 2.5rem;
  margin-top: 1.75rem;
  margin-bottom: 2.5rem;
  box-shadow:
    0 16px 24px 2px  rgba(0, 0, 0, 0.14),
    0 6px 30px  5px  rgba(0, 0, 0, 0.12),
    0 8px 10px  -5px rgba(0, 0, 0, 0.20);
}
/* When the container fills the viewport edge-to-edge:
   - drop the rounded corners (would be clipped at viewport edges)
   - glue the container's top to the bottom of the site header
   - remove the bottom margin and grow the container to fill any
     remaining viewport space below it (no gray gap at the bottom)
   - hide the header shadow by default; show it only when scrolled
     (handled by JS adding .is-scrolled to .site-header) */
@media (max-width: 1080px) {
  body {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
  }
  main.container {
    border-radius: 0;
    margin-top: 0;
    margin-bottom: 0;
    flex: 1 0 auto;
  }
}
@media (max-width: 720px) {
  /* Tighter horizontal padding on narrow screens — keep vertical padding
     unchanged so the photo's vertical position is identical to the
     ≤860px stacked layout (avoids the photo "rising" at this breakpoint).
     Margins are managed by the ≤1080px fullbleed block (margin-top/bottom: 0);
     don't override. */
  main.container {
    padding: 2rem 1.25rem;
  }
}

/* Header — light, with INF-UFRGS logo */
.site-header {
  background: var(--surface);
  color: var(--text);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--header-shadow);
  position: sticky;
  top: 0;
  z-index: 10;
}
.site-header a { color: var(--text); }
.site-header a:hover { text-decoration: none; }

/* Fullbleed (≤1080px): hide header shadow until the page is scrolled.
   Defined AFTER the base .site-header rule so it wins on cascade. */
@media (max-width: 1080px) {
  .site-header {
    box-shadow: none;
    transition: box-shadow 200ms ease;
  }
  .site-header.is-scrolled {
    box-shadow: var(--header-shadow);
  }
}

.header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding-top: 0.85rem;
  padding-bottom: 0.85rem;
  min-height: 56px; /* matches base logo height; ensures stable cross-axis */
  line-height: 1;   /* kill inherited body line-height ghost space */
}
.header-row > * {
  align-self: center;
  margin-top: 0;
  margin-bottom: 0;
}
.brand {
  display: flex;
  align-items: center;
  font-weight: 600;
  text-decoration: none;
  line-height: 0; /* prevent baseline whitespace below the img */
  /* Don't let the flex parent (.header-row) squeeze the brand below its
     intrinsic size — would distort the logo (compressed width with fixed
     height) at narrow viewports between mobile-nav breakpoint and desktop. */
  flex-shrink: 0;
}
.brand:hover { text-decoration: none; }
.brand-logo {
  /* Fixed 56px on desktop (no fluid scaling). The JS-driven nav-collapse
     in partials/header.njk fires the hamburger BEFORE space gets tight,
     so the logo never has to compete with the nav for room — it can stay
     at full size until the mobile breakpoint switches it to 44px. */
  height: 56px;
  width: auto;
  display: block;
  vertical-align: middle;
  /* Override the global `img { max-width: 100% }` rule so the logo can
     occupy its full intrinsic width inside a constrained flex parent. */
  max-width: none;
}

/* Light/dark logo swap. Both files are loaded so the swap is instant
   when the theme toggles (no flash); CSS hides the wrong one. */
.brand-logo-dark { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .brand-logo-light { display: none; }
  :root:not([data-theme="light"]) .brand-logo-dark  { display: block; }
}
:root[data-theme="dark"] .brand-logo-light { display: none; }
:root[data-theme="dark"] .brand-logo-dark  { display: block; }

/* Theme menu — last item in the nav. The trigger looks like a regular
   nav link (matching font, weight, and underline behaviour); clicking it
   reveals a small dropdown with three options (System / Dark / Light).
   In the collapsed (mobile) nav, the dropdown drops its absolute
   positioning and renders inline so it nests inside the hamburger
   layout naturally. */
.theme-menu {
  position: relative;
  /* Extra breathing room before the toggle so it reads as a separate
     control rather than just another nav item. Adds to the parent ul's
     gap (1.4rem), giving ~2.2rem total separation from the previous
     item. */
  margin-left: 0.8rem;
}
.theme-menu-trigger {
  /* Icon-only trigger that should ride the same line as the surrounding
     text nav items. We don't override `display`, so a <button> remains
     its default inline-block — which gives it the same line-box and
     baseline behaviour as <a> text items. The padding/border/font-size
     match .site-nav a exactly so the trigger's intrinsic height equals
     the text items' height.
     The icon colour is a 50/50 mix of --text-muted and --neutral-divider
     — a midtone grey that's softer than the bold body text but more
     visible than the hover underline grey. color-mix() interpolates
     correctly in both light and dark modes since both source variables
     are themed. */
  background: transparent;
  border: 0;
  font-family: inherit;
  font-size: 1.1875rem;
  font-weight: 600;
  color: color-mix(in srgb, var(--text-muted), var(--neutral-divider));
  padding: 0.45rem 0;
  border-bottom: 3px solid transparent;
  cursor: pointer;
  position: relative;
}
.theme-menu-trigger svg {
  width: 20px;
  height: 20px;
  /* vertical-align: middle puts the icon's centre on the line's
     baseline + half x-height — the same vertical position browsers
     use for inline images. No margin tweaks: any nudge would push the
     icon outside the natural line-box and pull the row taller, which
     would in turn shift sibling text items. */
  vertical-align: middle;
}
/* Icon-by-state: show only the icon matching the current mode. The
   visible span is plain inline so it doesn't establish its own line-box
   or flex context — the SVG inside falls back to inline-replaced
   behaviour and respects `vertical-align: middle` directly. */
.theme-menu-trigger .theme-toggle-icon { display: none; }
.theme-menu-trigger .theme-toggle-icon-system { display: inline; }
:root[data-theme="light"] .theme-menu-trigger .theme-toggle-icon-system { display: none; }
:root[data-theme="light"] .theme-menu-trigger .theme-toggle-icon-sun    { display: inline; }
:root[data-theme="dark"]  .theme-menu-trigger .theme-toggle-icon-system { display: none; }
:root[data-theme="dark"]  .theme-menu-trigger .theme-toggle-icon-moon   { display: inline; }
/* Mobile (collapsed) nav: trigger becomes a full-width row matching
   the other hamburger items (same padding, hover tint, no underline). */
body.nav-collapsed .theme-menu-trigger {
  font-size: var(--text-base);
  width: 100%;
  display: flex;
  justify-content: space-between;
  margin: 0 -0.75rem;
  padding: 0.65rem 0.75rem;
  border-bottom: none;
  text-align: left;
  transition: background-color 0.12s ease-out;
}
/* Suppress hover tooltip on mobile (where touch can't trigger :hover
   anyway, and the bubble would overlap the next nav item below). */
body.nav-collapsed .theme-menu-trigger::before,
body.nav-collapsed .theme-menu-trigger::after { display: none; }
body.nav-collapsed .theme-menu-trigger:hover,
body.nav-collapsed .theme-menu-trigger:active,
body.nav-collapsed .theme-menu-trigger[aria-expanded="true"] {
  background-color: rgba(0, 0, 0, 0.04);
}
/* Hover / open feedback — subtle colour shift on the icon (no underline,
   since the trigger is icon-only and an underline would dangle). */
.theme-menu-trigger:hover,
.theme-menu-trigger[aria-expanded="true"] {
  color: var(--inf-red);
}
.theme-menu-trigger:focus-visible {
  outline: 2px solid var(--ufrgs-blue-light);
  outline-offset: 2px;
  border-radius: 2px;
}

/* Tooltip — same visual vocabulary as .pub-link-icon's tooltip, but
   anchored BELOW the trigger so it doesn't get clipped against the
   header's top edge. Hidden whenever the dropdown is already open
   (no point repeating "Theme" while the menu is showing). */
.theme-menu-trigger::before {
  content: attr(data-tooltip);
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) translateY(-3px);
  background: var(--text);
  color: var(--surface);
  padding: 0.3rem 0.55rem;
  border-radius: var(--radius);
  font-size: 0.72rem;
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 5;
}
.theme-menu-trigger::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 4px solid transparent;
  border-bottom-color: var(--text);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.theme-menu-trigger:hover::before,
.theme-menu-trigger:focus-visible::before,
.theme-menu-trigger:hover::after,
.theme-menu-trigger:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.theme-menu-trigger:focus-visible::after { transform: translateX(-50%); }
/* Suppress the tooltip while the menu is open — the dropdown itself is
   the relevant feedback at that point. */
.theme-menu-trigger[aria-expanded="true"]::before,
.theme-menu-trigger[aria-expanded="true"]::after {
  opacity: 0;
}

/* Dropdown panel (desktop).
   Anchored so its right edge lines up with the trigger's right edge —
   safest for narrow desktops where centering would clip the right side
   of the viewport. The caret triangle (::before) is offset to point at
   the trigger icon's centre, giving the visual impression of "centred
   beneath" without the clipping risk. */
.theme-menu-dropdown {
  position: absolute;
  top: calc(100% + 0.55rem); /* room for the caret to live above the panel */
  right: 0;
  min-width: 150px;          /* was 180; trimmed for the short labels */
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow:
    0 8px 24px -6px rgba(0, 0, 0, 0.18),
    0 4px 12px -4px rgba(0, 0, 0, 0.12);
  list-style: none;
  margin: 0;
  /* Comfortable vertical padding — the option block sits visually
     centred between the panel's top and bottom edges with clearly
     more breathing room above/below than between adjacent items. */
  padding: 0.6rem 0.3rem;
  z-index: 50;
  font-size: var(--text-sm);
  font-weight: 500;
  display: flex;
  flex-direction: column;
  gap: 0; /* items meet edge-to-edge — visually denser without crowding */
  /* Open animation — fades and slides down ~5px from above the trigger.
     Triggered every time `[hidden]` is removed by JS (the attribute
     change makes the element render again, restarting the animation). */
  transform-origin: top right;
  animation: themeMenuOpen 0.14s ease-out;
}
.theme-menu-dropdown[hidden] { display: none; }

@keyframes themeMenuOpen {
  from { opacity: 0; transform: translateY(-5px); }
  to   { opacity: 1; transform: translateY(0);    }
}

/* Caret pointing up to the trigger icon. A 12×12 square rotated 45°
   gives a clean diamond with the surface fill and matching border. */
.theme-menu-dropdown::before {
  content: "";
  position: absolute;
  top: -7px;
  /* Aligns horizontally with the trigger icon centre: trigger right edge
     coincides with dropdown right edge, icon is ~20px wide centred in the
     trigger, so the icon centre sits 10px from the dropdown's right edge.
     A 12px diamond centred there → right: 4px (10 - 12/2). */
  right: 4px;
  width: 12px;
  height: 12px;
  background: var(--surface);
  border-top: 1px solid var(--border);
  border-left: 1px solid var(--border);
  transform: rotate(45deg);
}

.theme-menu-item {
  width: 100%;
  background: transparent;
  border: 0;
  /* Vertical padding tuned so:
       - Distance between adjacent item contents (2× this padding ≈ 19px)
         pairs naturally with the panel's edge breathing room (panel
         padding 0.6rem + item padding 0.6rem ≈ 19.2px), giving a fully
         even rhythm down the list.
       - Each row is ~36px tall — comfortable click target, matches the
         proportion common in macOS-style menus (rows around 28–36px). */
  padding: 0.6rem 0.75rem;
  border-radius: calc(var(--radius) - 2px);
  display: flex;
  align-items: center;
  gap: 0.6rem;
  cursor: pointer;
  color: var(--text);
  font: inherit;
  text-align: left;
  white-space: nowrap;
  transition: background-color 0.1s ease-out;
}
.theme-menu-item:hover {
  /* Stronger than --bg-soft (which is only ~4% darker than --surface and
     reads as nearly invisible against the panel). 8% mix of text into
     surface yields a clearly perceptible row tint while staying calm —
     and the percentage works in both light and dark modes since
     color-mix interpolates the themed variables on each side. */
  background: color-mix(in srgb, var(--surface), var(--text) 8%);
}
.theme-menu-item:focus-visible {
  outline: 2px solid var(--ufrgs-blue-light);
  outline-offset: -2px;
}
.theme-menu-item svg {
  width: 18px;
  height: 18px;
  display: block;
  transition: color 0.1s ease-out;
}
.theme-menu-item-icon {
  display: inline-flex;
  flex-shrink: 0;
  /* Same midtone grey as the trigger icon in the nav, for visual unity. */
  color: color-mix(in srgb, var(--text-muted), var(--neutral-divider));
}
/* Hover: icon flips to the INF red, matching the colour the trigger
   itself takes on hover. The grey row background plus the red icon
   makes the hovered row pop without needing extra weight on the label. */
.theme-menu-item:hover .theme-menu-item-icon,
.theme-menu-item:focus-visible .theme-menu-item-icon {
  color: var(--inf-red);
}
.theme-menu-item-label { flex: 1 1 auto; }
.theme-menu-item-check {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--inf-red);
  visibility: hidden; /* Reserve space so the active item doesn't shift. */
}
/* Active item — only the red check on the right indicates active state;
   no background change, keeping the dropdown calm and uniform. */
.theme-menu-item.is-active .theme-menu-item-check { visibility: visible; }

/* Dark mode: the default soft shadow disappears against a dark surface,
   so we boost the border a step (--neutral-divider ≈ #4a4c50) and add a
   tiny inset highlight along the top edge to fake ambient light coming
   from above — gives the panel back its sense of depth. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-menu-dropdown {
    border-color: var(--neutral-divider);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.06),
      0 8px 24px -6px rgba(0, 0, 0, 0.5),
      0 4px 12px -4px rgba(0, 0, 0, 0.35);
  }
  :root:not([data-theme="light"]) .theme-menu-dropdown::before {
    border-color: var(--neutral-divider);
  }
}
:root[data-theme="dark"] .theme-menu-dropdown {
  border-color: var(--neutral-divider);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 8px 24px -6px rgba(0, 0, 0, 0.5),
    0 4px 12px -4px rgba(0, 0, 0, 0.35);
}
:root[data-theme="dark"] .theme-menu-dropdown::before {
  border-color: var(--neutral-divider);
}

/* Mobile (collapsed nav): the dropdown drops absolute positioning and
   renders inline as an indented sub-list, since the hamburger is
   already a vertical stack. The caret, animation, and elevated panel
   styling all turn off so the sub-menu reads as a continuation of the
   hamburger menu instead of a floating popover. */
body.nav-collapsed .theme-menu-dropdown {
  position: static;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  padding: 0.25rem 0 0.25rem 1.25rem;
  min-width: 0;
  margin-top: 0.2rem;
  animation: none;
}
body.nav-collapsed .theme-menu-dropdown::before { display: none; }
body.nav-collapsed .theme-menu-item {
  padding: 0.5rem 0.75rem;
}

.site-nav-mobile { display: none; }
/* Outer nav lists only — the :not() guard keeps these styles from
   reaching down into the nested .theme-menu-dropdown <ul>, which is
   *also* a descendant of .site-nav and was being silently overridden
   by these rules (specificity 0,1,1 vs the dropdown's 0,1,0). Without
   the guard, every padding/gap I set on .theme-menu-dropdown was
   getting trumped by `padding: 0; gap: 1.4rem` here. */
.site-nav ul:not(.theme-menu-dropdown) {
  list-style: none;
  display: flex;
  align-items: center; /* keep all nav items centred on the row in case a
                          theme-trigger-driven height difference appears */
  gap: 1.4rem;
  margin: 0;
  padding: 0;
}
.site-nav a {
  /* Fixed 19px (rather than var(--text-base)) so the inline nav doesn't
     shrink at the typography breakpoint while it's still visible. The
     mobile-collapsed override below restores --text-base for the
     hamburger menu, where smaller body-sized text is appropriate. */
  font-size: 1.1875rem;
  padding: 0.45rem 0;
  border-bottom: 3px solid transparent; /* keeps layout stable */
  color: var(--text);
  font-weight: 600;
  white-space: nowrap;
  position: relative;
}
body.nav-collapsed .site-nav a {
  /* Mobile hamburger: scale with --text-base (which the typography
     breakpoint at 860px reduces to 17px). */
  font-size: var(--text-base);
}
.site-nav a::after {
  content: "";
  position: absolute;
  left: 50%;
  right: 50%;
  bottom: 2px;
  height: 3px;
  background: var(--neutral-divider);
}
.site-nav a:hover::after {
  left: 0;
  right: 0;
  transition: left 0.12s ease-out, right 0.12s ease-out;
}
.site-nav a[aria-current="page"]::after {
  left: 0;
  right: 0;
  background: var(--inf-red);
}

/* Mobile nav toggle */
.nav-toggle {
  display: none;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  width: 44px;
  height: 44px;
  padding: 10px 12px;
  margin: 0;
  flex-direction: column;
  justify-content: space-between;
  align-self: center;
  cursor: pointer;
  box-sizing: border-box;
  line-height: 0;
  font-family: inherit;
}
.nav-toggle-bar {
  display: block;
  height: 2px;
  background: var(--ufrgs-blue);
  border-radius: 1px;
}
/* Dark mode: switch the hamburger bars to INF red so they remain a
   recognisable brand accent against the dark header (the lifted blue
   used on light backgrounds reads as just another grey on dark ones). */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .nav-toggle-bar { background: var(--inf-red); }
}
:root[data-theme="dark"] .nav-toggle-bar { background: var(--inf-red); }

.muted { color: var(--text-muted); }

/* Page header (for inner pages) */
.page-header {
  padding: 1rem 0 1rem;
  border-bottom: 2px solid var(--inf-red);
  margin-bottom: 2rem;
}
.page-header h1 { margin-bottom: 0.25rem; }
.page-header .lead { color: var(--text-muted); font-size: var(--text-md); margin: 0; }
.page-header-heading {
  display: flex;
  align-items: center;
  gap: 0.7rem;
}
.page-header-heading h1 { margin: 0; }
.page-header-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  color: var(--inf-red);
}
.page-header-icon svg {
  width: 1.6em;
  height: 1.6em;
  display: block;
}
.page-header-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem;
  flex-wrap: wrap;
}
.page-header-titles { flex: 1 1 auto; min-width: 0; }
.page-header-links { margin: 0; flex: 0 0 auto; padding-bottom: 0.25rem; }
.page-body { padding-bottom: 0; }

/* Hero (home) */
.hero {
  padding: 2.5rem 0 2rem;
  border-bottom: 2px solid var(--inf-red);
}
.hero-grid {
  display: grid;
  grid-template-columns: 200px minmax(0, 32rem);
  gap: 2rem;
  align-items: center;
  justify-content: center;
}
.hero-text { line-height: 1.5; }
.hero-text p { margin: 0 0 0.6em; }
.hero-text p:last-child { margin-bottom: 0; }
.profile-photo {
  width: 200px;
  height: auto;
  object-fit: cover;
  display: block;
  background: var(--bg-soft);
  border-radius: var(--radius);
  box-shadow:
    0 10px 20px -8px rgba(0, 0, 0, 0.25),
    0 6px 18px  0    rgba(0, 0, 0, 0.10),
    0 2px 4px  -2px rgba(0, 0, 0, 0.10);
}
.address { color: var(--text-muted); font-size: var(--text-sm); }
.profile-links { margin-top: 0.4rem; font-size: var(--text-sm); }
.profile-links a { font-weight: 600; }

/* MSc & TCC theses page — intro paragraph. */
.theses-intro {
  margin-bottom: 2rem;
}
.theses-intro p {
  margin: 0 0 0.85rem;
}
.theses-intro p:last-child {
  margin-bottom: 0;
}

/* MSc & TCC theses page — collapsible accordion list of thesis topics. */
.theses-list { list-style: none; padding: 0; margin: 0; }
.theses-list > li { margin: 0; }
/* no separator between .thesis-item entries — the +/− marker on summary
   already provides enough visual separation */
.thesis-item > summary {
  cursor: pointer;
  list-style: none;
  padding: 0.85rem 0 0.85rem 1.75rem;
  position: relative;
  font-weight: 600;
  line-height: 1.4;
  transition: color 0.15s ease-out;
}
.thesis-item > summary::-webkit-details-marker { display: none; }
.thesis-item > summary::before {
  content: "+";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 1.25rem;
  height: 1.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  color: var(--inf-red);
  font-size: 1.25em;
  line-height: 1;
  transition: transform 0.2s ease-out;
}
.thesis-item[open] > summary::before {
  content: "−";
}
.thesis-item > summary:hover { color: var(--inf-red); }
/* English title keeps the default text color even when the summary is
   hovered — only the Portuguese subtitle and the +/− marker get the
   red hover treatment. */
.thesis-item > summary:hover .thesis-title { color: var(--text); }
.thesis-title { display: block; font-weight: 600; color: var(--text); }
.thesis-title-pt {
  display: block;
  font-weight: 400;
  color: var(--text-muted);
  font-size: var(--text-sm);
}
.thesis-body {
  margin: 0.25rem 0 1rem 1.75rem;
  padding: 0.25rem 0 0.25rem 1rem;
  border-left: 2px solid var(--neutral-divider);
  color: var(--text);
  font-size: var(--text-sm);
}
.thesis-body p { margin: 0 0 0.75rem; }
.thesis-body p:last-child { margin-bottom: 0; }
.thesis-body ol, .thesis-body ul { padding-left: 1.5rem; margin: 0.5rem 0; }
.thesis-body li { margin-bottom: 0.4rem; }
/* Two language sections per thesis (English + Portuguese), separated by
   a subtle horizontal divider between them. */
.thesis-lang { margin-bottom: 1.75rem; }
.thesis-lang:last-child { margin-bottom: 0; }
.thesis-lang + .thesis-lang {
  padding-top: 1.5rem;
  border-top: 1px solid var(--neutral-divider);
}
/* Language tag inside a thesis description ("English" / "Português").
   Built on the standard .tag chip — styled inline-block by .tag — and
   given a small block of breathing room below it before the prose
   paragraph that follows. */
.tag-lang {
  margin-bottom: 0.6rem;
}

/* /publications-bib/ — hidden development preview powered by bib.bib.
   The view-toggle sits below the page header and above the publication
   listings; styled as a segmented control matching the site's restraint. */
/* Single horizontal row that pairs the view toggle with the active
   view's "Jump to" dropdown — both sit at the start of the row,
   separated only by the flex gap, so they read as a coupled control
   set rather than as two anchored extremes of the page. On narrow
   screens the row wraps, dropping the jump-to below the toggle. */
.pub-controls {
  display: flex;
  align-items: center;
  gap: 1rem 1.5rem;
  flex-wrap: wrap;
  margin: 0 0 2rem;
}
/* Inside .pub-controls, both children manage their own internal layout
   but the outer flex row owns the spacing — kill the legacy bottom
   margins that used to separate them when they were stacked. */
.pub-controls .pub-view-toggle,
.pub-controls .pub-jump {
  margin: 0;
}

.pub-view-toggle {
  display: inline-flex;
  border: 1px solid var(--neutral-divider);
  border-radius: var(--radius);
  overflow: hidden;
  margin: 0 0 2rem;
}
.pub-view-toggle-btn {
  background: transparent;
  border: 0;
  padding: 0.5rem 1.1rem;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-muted);
  cursor: pointer;
  transition: background-color 0.12s ease-out, color 0.12s ease-out;
}
.pub-view-toggle-btn + .pub-view-toggle-btn {
  border-left: 1px solid var(--neutral-divider);
}
.pub-view-toggle-btn:hover { color: var(--text); }
.pub-view-toggle-btn.is-active {
  background: var(--inf-red);
  color: #fff;
}
.pub-view-toggle-btn.is-active:hover { color: #fff; }
.pub-view-toggle-btn:focus-visible {
  outline: 2px solid var(--inf-red);
  outline-offset: 2px;
}
/* Floating "back to top" button. Available globally (rendered in base.njk),
   it stays hidden until the user has scrolled past a threshold (~600px).
   On short pages where that scroll never happens it stays invisible with
   no visual cost; on long pages (publications, students/past, etc.) it
   provides a one-tap return to the top. */
.back-to-top {
  position: fixed;
  bottom: 1.5rem;
  right: 1.5rem;
  width: 2.75rem;
  height: 2.75rem;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--neutral-divider);
  background: var(--surface);
  color: var(--text-muted);
  cursor: pointer;
  z-index: 50;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
  transition: color 0.15s, border-color 0.15s, transform 0.15s;
}
.back-to-top[hidden] { display: none; }
.back-to-top:hover,
.back-to-top:focus-visible {
  color: var(--inf-red);
  border-color: var(--inf-red);
  transform: translateY(-2px);
  outline: none;
}
.back-to-top svg { width: 18px; height: 18px; }

/* Jump-to menu for /publications/. Custom dropdown that mirrors the
   dark-mode theme menu's panel/option styling, so the controls bar at
   the top of the page reads as a coherent UI vocabulary across the site.
   The trigger looks like a bordered select control; clicking it opens
   a popover panel anchored below it with the year / research-line
   options. Selecting an option smooth-scrolls to the target section. */
.pub-jump {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
  position: relative; /* anchor for the absolute-positioned dropdown */
}
/* Explicit override — the rule above sets `display: flex` (specificity
   0,1,0), which beats the user-agent `[hidden] { display: none }` of
   the same specificity (author > UA on tie). Without this line, the
   JS-toggled `hidden` attribute is silently ignored and both jump-to
   menus stay visible at once. */
.pub-jump[hidden] { display: none; }
.pub-jump-label {
  font-weight: 600;
  color: var(--text);
}

/* Trigger button — visually similar to a native <select> (border,
   chevron, hover) so users immediately understand it as a chooser. */
.pub-jump-trigger {
  font: inherit;
  font-size: var(--text-sm);
  padding: 0.4rem 0.75rem;
  border: 1px solid var(--neutral-divider);
  border-radius: var(--radius);
  background-color: var(--surface);
  color: var(--text);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 14rem;
  max-width: 100%;
  text-align: left;
  transition: border-color 0.12s ease-out;
}
.pub-jump-trigger-text {
  flex: 1 1 auto;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pub-jump-trigger-chevron {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--text-muted);
  transition: transform 0.15s ease-out;
}
.pub-jump-trigger-chevron svg {
  width: 14px;
  height: 14px;
  display: block;
}
.pub-jump-trigger[aria-expanded="true"] .pub-jump-trigger-chevron {
  transform: rotate(180deg);
}
.pub-jump-trigger:hover { border-color: var(--text-muted); }
.pub-jump-trigger:focus-visible {
  outline: 2px solid var(--inf-red);
  outline-offset: 1px;
}

/* Dropdown panel — same visual vocabulary as the theme menu dropdown
   (surface bg, border, shadow, caret, animation). Anchored to the
   trigger via .pub-jump's position:relative. Year lists can be 25+
   entries, so the panel is height-capped with internal scrolling. */
.pub-jump-dropdown {
  position: absolute;
  top: calc(100% + 0.55rem);
  left: 0;
  min-width: 100%;
  max-height: 320px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow:
    0 8px 24px -6px rgba(0, 0, 0, 0.18),
    0 4px 12px -4px rgba(0, 0, 0, 0.12);
  list-style: none;
  margin: 0;
  padding: 0.6rem 0.3rem;
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 0;
  transform-origin: top left;
  animation: themeMenuOpen 0.14s ease-out;
}
.pub-jump-dropdown[hidden] { display: none; }

/* Caret pointing up to the trigger. Anchored to the LEFT side this
   time (left: 16px) since the panel is left-aligned with the trigger;
   places the diamond roughly under the trigger label start. */
.pub-jump-dropdown::before {
  content: "";
  position: absolute;
  top: -7px;
  left: 16px;
  width: 12px;
  height: 12px;
  background: var(--surface);
  border-top: 1px solid var(--border);
  border-left: 1px solid var(--border);
  transform: rotate(45deg);
}

/* Option button — matches .theme-menu-item exactly for visual unity.
   The explicit `line-height: 1` is the critical line: theme-menu items
   sit inside .header-row (which sets line-height: 1 to kill body
   line-height ghost space), so they inherit a tight 1× line-box. The
   pub-jump dropdown lives in the page body where the inherited line-
   height is 1.65 — without this override, each row would be ~11px
   taller than its theme-menu twin, breaking the visual symmetry. */
.pub-jump-option {
  width: 100%;
  background: transparent;
  border: 0;
  padding: 0.6rem 0.75rem;
  border-radius: calc(var(--radius) - 2px);
  cursor: pointer;
  color: var(--text);
  font: inherit;
  font-size: var(--text-sm);
  line-height: 1;
  text-align: left;
  white-space: nowrap;
  transition: background-color 0.1s ease-out;
}
.pub-jump-option:hover,
.pub-jump-option:focus-visible {
  background: color-mix(in srgb, var(--surface), var(--text) 8%);
  outline: none;
}

/* Dark mode: lift the panel border + restore depth via inset highlight,
   matching the theme menu treatment. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .pub-jump-dropdown {
    border-color: var(--neutral-divider);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.06),
      0 8px 24px -6px rgba(0, 0, 0, 0.5),
      0 4px 12px -4px rgba(0, 0, 0, 0.35);
  }
  :root:not([data-theme="light"]) .pub-jump-dropdown::before {
    border-color: var(--neutral-divider);
  }
}
:root[data-theme="dark"] .pub-jump-dropdown {
  border-color: var(--neutral-divider);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.06),
    0 8px 24px -6px rgba(0, 0, 0, 0.5),
    0 4px 12px -4px rgba(0, 0, 0, 0.35);
}
:root[data-theme="dark"] .pub-jump-dropdown::before {
  border-color: var(--neutral-divider);
}

/* Site footer — single discreet line with institutional credit, profile
   links, and a build-time "last updated" stamp. Sits BELOW the white
   <main class="container"> card, on the gray body background, so it's
   styled as faded muted text without a top border (the card edge already
   provides visual separation). */
.site-footer {
  /* margin-top: 1.5rem keeps a comfortable gap on mobile (where the
     fullbleed card has margin-bottom: 0). On desktop the card's larger
     margin-bottom (2.5rem) wins margin-collapse, so spacing is unchanged. */
  margin: 1.5rem auto 2rem;
  padding: 0 1.25rem;
  width: 100%;
  max-width: var(--container);
}
.site-footer-line {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
  line-height: 1.6;
  text-align: center;
}
.site-footer-sep {
  margin: 0 0.4em;
  color: var(--neutral-divider);
}
.site-footer-stamp { white-space: nowrap; }
@media (max-width: 720px) {
  /* on narrow screens, soften the separators so the line wraps cleanly */
  .site-footer-sep { margin: 0 0.25em; }
}

/* 404 / error page — friendly hint list and a contact prompt. */
.error-page p { margin: 0 0 1rem; }
.error-page p:last-child { margin-top: 1.5rem; color: var(--text-muted); }
.error-links {
  list-style: none;
  padding: 0;
  margin: 0.5rem 0 1.5rem;
}
.error-links li {
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--neutral-divider);
}
.error-links li:last-child { border-bottom: 0; }
.error-links a { font-weight: 600; }

/* Students page — short link to the (Portuguese) graduate-students FAQ,
   sitting at the top of the page above the kind headings. */
.students-faq-link {
  margin: 0 0 1.75rem;
  font-size: var(--text-sm);
  color: var(--text-muted);
}
.students-faq-link a { font-weight: 600; }

/* FAQ page — collapsible Q&A list. Uses the same accordion treatment as the
   theses page (+/− marker, animated open/close, left-border body) but with
   FAQ-specific tweaks: a question numbering chip and a slightly tighter body.
*/
.faq-intro { margin: 0 0 1.5rem; color: var(--text-muted); }
.faq-intro p { margin: 0; }
.faq-list { list-style: none; padding: 0; margin: 0 0 1rem; }
.faq-list > li { margin: 0; }
.faq-item > summary {
  cursor: pointer;
  list-style: none;
  padding: 0.85rem 0 0.85rem 1.75rem;
  position: relative;
  font-weight: 600;
  line-height: 1.4;
  transition: color 0.15s ease-out;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
}
.faq-item > summary::-webkit-details-marker { display: none; }
.faq-item > summary::before {
  content: "+";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 1.25rem;
  height: 1.25rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  color: var(--inf-red);
  font-size: 1.25em;
  line-height: 1;
  transition: transform 0.2s ease-out;
}
.faq-item[open] > summary::before { content: "−"; }
.faq-num {
  font-variant-numeric: tabular-nums;
  color: var(--text-muted);
  font-weight: 600;
  flex-shrink: 0;
  min-width: 2ch;
}
.faq-question { display: inline-block; }
.faq-body {
  margin: 0 0 1rem 1.75rem;
  padding: 0.25rem 0 0.25rem 1rem;
  border-left: 2px solid var(--neutral-divider);
  color: var(--text);
  font-size: var(--text-sm);
}
.faq-body p { margin: 0 0 0.75rem; }
.faq-body p:last-child { margin-bottom: 0; }
.faq-body a { font-weight: 600; }
.faq-body ul.faq-topics {
  list-style: none;
  padding: 0;
  margin: 0.75rem 0 0;
}
.faq-body ul.faq-topics > li {
  margin: 0 0 0.85rem;
  padding-left: 1rem;
  position: relative;
}
.faq-body ul.faq-topics > li::before {
  content: "—";
  position: absolute;
  left: 0;
  color: var(--text-muted);
}
.faq-body ul.faq-topics > li:last-child { margin-bottom: 0; }

/* Inline contact icons (mail, phone) — sit on the text baseline,
   sized to match the text, in a muted color so they don't dominate. */
.contact-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  color: var(--text-muted);
}
.contact-icon svg { width: 1em; height: 1em; display: block; }

/* Contact block: email line + phone row, tightly spaced (overrides the
   default 0.6em paragraph spacing in .hero-text — needs the .hero-text
   ancestor selector for sufficient specificity). */
.hero-text .contact-block > * { margin: 0; }
.hero-text .contact-block > * + * { margin-top: 0.15em; }

/* Hero affiliation line: "Institute of Informatics — Computer Networks Group".
   When the hero is laid out side-by-side (photo left, text right) the dash
   keeps both affiliations on a single line. When the hero stacks vertically
   (photo on top, text below — i.e. the ≤860px breakpoint) the dash is
   hidden and the line is split in two via the .hero-affil-break <br>. */
.hero-affil-break { display: none; }
@media (max-width: 860px) {
  .hero-affil-sep { display: none; }
  .hero-affil-break { display: inline; }
}

/* Phone row on the home: Phone label on the left, profile links on the right.
   Width = 100% of the hero-text column (which is sized by its widest line),
   so the right edge of profile-links aligns with the right edge of the
   widest information line above. align-items: baseline keeps the two spans
   aligned on their text baseline. */
.phone-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  width: 100%;
  flex-wrap: wrap;
}
.phone-row .profile-links { margin: 0; }
.topics { columns: 2; column-gap: 2rem; }
@media (max-width: 720px) { .topics { columns: 1; } }
.activities li { margin-bottom: 0.5rem; }
.activities a { word-break: break-word; }

/* Em-dash bullets for prose lists */
.activities,
.topics {
  list-style: none;
  padding-left: 0;
}
.activities > li,
.topics > li {
  position: relative;
  padding-left: 1.5rem;
}
.activities > li::before,
.topics > li::before {
  content: "—";
  position: absolute;
  left: 0;
  color: var(--text-muted);
}

.see-more {
  margin-top: 1rem;
  margin-bottom: 2rem;
  font-size: var(--text-sm);
}
.see-more a { font-weight: 600; }

.back-link {
  margin-top: 0.25rem;
  margin-bottom: 1.25rem;
  font-size: var(--text-sm);
}
.back-link + .activities { margin-top: 0.25rem; }

/* Year jump nav for publications */
.year-jump {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.6rem 0.85rem;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  margin: 1rem 0 1.5rem;
  position: sticky;
  top: 84px;
  z-index: 5;
}
.year-jump a {
  padding: 0.2rem 0.55rem;
  border-radius: var(--radius-pill);
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text);
  text-decoration: none;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
  transition: background-color 0.15s ease, color 0.15s ease,
              border-color 0.15s ease, transform 0.15s ease;
}
.year-jump a:hover {
  background: var(--inf-red);
  color: var(--surface);
  border-color: var(--inf-red);
  transform: translateY(-1px);
}
.year-jump a:active { transform: translateY(0); }

.pub-year-block { scroll-margin-top: var(--pub-scroll-margin, 200px); }
.pub-year-block h2,
.kind-heading {
  margin-top: 2.5rem;
}
.pub-citation { margin: 0 0 0.2rem; font-size: var(--text-base); }
.pub-citation .pub-authors { color: var(--text); }
.pub-citation .pub-title-text { font-weight: 600; }
.pub-citation .pub-venue { color: inherit; }
.pub-links { margin: 0 0 0.4rem; font-size: var(--text-sm); display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.pub-link {
  display: inline-block;
  color: var(--ufrgs-blue);
  font-weight: 500;
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  font-size: inherit;
  cursor: pointer;
  text-decoration: none;
}
.pub-link:hover { color: var(--inf-red); }
/* Show the focus outline only on keyboard focus (Tab), not on mouse click —
   the click triggers focus too, which would otherwise leave a red rectangle
   around the icon after every BibTeX expand/collapse. */
.pub-bibtex-toggle:focus-visible { outline: 2px solid var(--inf-red); outline-offset: 1px; }

/* Icon-only pub links (PDF, DOI, BibTeX). Tooltip on hover/focus via the
   data-tooltip attribute. */
.pub-link-icon {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.2rem 0.25rem;
  line-height: 1;
}
.pub-link-icon svg {
  width: 16px;
  height: 16px;
  display: block;
}
.pub-link-icon::before {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) translateY(3px);
  background: var(--text);
  color: var(--surface);
  padding: 0.3rem 0.55rem;
  border-radius: var(--radius);
  font-size: 0.72rem;
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 5;
}
.pub-link-icon::after {
  content: "";
  position: absolute;
  bottom: calc(100% + 0px);
  left: 50%;
  transform: translateX(-50%);
  border: 4px solid transparent;
  border-top-color: var(--text);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.pub-link-icon:hover::before,
.pub-link-icon:focus-visible::before,
.pub-link-icon:hover::after,
.pub-link-icon:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.pub-link-icon:focus-visible::after { transform: translateX(-50%); }

/* Animated collapsible BibTeX block. Three-element technique:
   - .pub-bibtex-wrap: grid container; transitions grid-template-rows
     from 0fr → 1fr (no JS height measurement needed).
   - .pub-bibtex-inner: grid item with min-height:0 and overflow:hidden;
     this is what actually clips the pre's padding/border at zero height
     (without an inner element, the pre's padding-top + border-top would
     remain visible as a strip when collapsed).
   - .pub-bibtex: the styled visible block (padding, border, background). */
.pub-bibtex-wrap {
  display: grid;
  grid-template-rows: 0fr;
  margin-top: 0;
  transition: grid-template-rows 0.25s ease, margin-top 0.25s ease;
  position: relative; /* anchor for the absolute copy button + tooltip */
}
.pub-bibtex-wrap.is-open {
  grid-template-rows: 1fr;
  margin-top: 0.4rem;
}
.pub-bibtex-inner {
  min-height: 0;
  overflow: hidden;
}
.pub-bibtex-wrap.is-open .pub-bibtex-inner {
  overflow-x: hidden;
}

/* Copy-to-clipboard button on the BibTeX block. Lives outside the
   .pub-bibtex-inner so its tooltip isn't clipped by the inner's
   overflow:hidden (which is required for the collapse animation). */
.pub-bibtex-copy {
  position: absolute;
  top: 0.4rem;
  right: 0.4rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.3rem 0.5rem;
  cursor: pointer;
  color: var(--text-muted);
  font-size: 0.72rem;
  font-family: var(--font-body);
  font-weight: 600;
  line-height: 1;
  z-index: 2;
  visibility: hidden;
  opacity: 0;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease,
              opacity 0.2s ease 0.1s, visibility 0s ease 0.3s;
}
.pub-bibtex-wrap.is-open .pub-bibtex-copy {
  visibility: visible;
  opacity: 1;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease,
              opacity 0.2s ease 0.15s, visibility 0s ease 0s;
}
.pub-bibtex-copy:hover {
  color: var(--text);
  border-color: var(--text-muted);
}
.pub-bibtex-copy:focus-visible {
  outline: 2px solid var(--inf-red);
  outline-offset: 1px;
}
.pub-bibtex-copy svg { width: 14px; height: 14px; }
.pub-bibtex-copy-default,
.pub-bibtex-copy-success {
  display: inline-flex;
  align-items: center;
}
.pub-bibtex-copy-success { display: none; }
.pub-bibtex-copy.is-copied .pub-bibtex-copy-default { display: none; }
.pub-bibtex-copy.is-copied .pub-bibtex-copy-success {
  display: inline-flex;
  /* Quick pop-in: fade + small scale, gives the check a tactile arrival
     feel similar to Claude's chat code-copy buttons. */
  animation: bibtex-copy-pop 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes bibtex-copy-pop {
  0%   { opacity: 0; transform: scale(0.5); }
  60%  { opacity: 1; transform: scale(1.15); }
  100% { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .pub-bibtex-copy.is-copied .pub-bibtex-copy-success { animation: none; }
}
.pub-bibtex-copy.is-copied {
  color: #2a8a3f;
  border-color: #2a8a3f;
  background: rgba(42, 138, 63, 0.08);
}
/* Tooltip on hover/focus, hidden during the post-copy success state. */
.pub-bibtex-copy::before {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 8px);
  right: 0;
  background: var(--text);
  color: var(--surface);
  padding: 0.3rem 0.55rem;
  border-radius: var(--radius);
  font-size: 0.72rem;
  font-weight: 500;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translateY(3px);
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.pub-bibtex-copy::after {
  content: "";
  position: absolute;
  bottom: calc(100% + 2px);
  right: 0.6rem;
  border: 4px solid transparent;
  border-top-color: var(--text);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
}
.pub-bibtex-copy:hover::before,
.pub-bibtex-copy:focus-visible::before,
.pub-bibtex-copy:hover::after,
.pub-bibtex-copy:focus-visible::after {
  opacity: 1;
  transform: translateY(0);
}
.pub-bibtex-copy.is-copied::before,
.pub-bibtex-copy.is-copied::after {
  display: none;
}
.pub-bibtex {
  margin: 0;
  padding: 0.75rem 0.9rem;
  background: var(--bg-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.82rem;
  line-height: 1.5;
  white-space: pre-wrap;     /* preserve newlines, but wrap long lines */
  overflow-wrap: anywhere;   /* break long unbroken tokens (URLs, hashes) */
  word-break: break-word;    /* fallback for older browsers */
  color: var(--text);
}
.pub-bibtex code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  background: none;
  padding: 0;
  color: inherit;
}

/* Awards */
.awards-list { list-style: none; padding: 0; }
.awards-list li {
  display: grid;
  grid-template-columns: 60px 1fr;
  gap: 0.75rem;
  padding: 0.85rem 0;
  align-items: baseline;
  line-height: 1.4;
}
.awards-list .award-year {
  font-weight: 500;
  color: var(--inf-red);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}
.awards-list .award-entry + .award-entry { margin-top: 0.6rem; }
.awards-list .award-name { font-weight: 600; display: block; }
.awards-list .award-entity { color: var(--text-muted); font-size: var(--text-sm); display: block; }
.awards-list .activity-name { display: block; }
.awards-list .activity-meta { color: var(--text-muted); font-size: var(--text-sm); display: block; }

.hero h1 { margin-top: 0; margin-bottom: 0.25rem; }
.lead { font-size: var(--text-md); color: var(--text-muted); }

/* Sections */
.section { padding: 2rem 0 2.5rem; }

/* Home bio paragraph: justified text with automatic hyphenation for a
   formal, journal-like appearance. The <html> lang="en" attribute lets
   the browser hyphenate English correctly. On narrow screens the
   justification creates visible word-spacing irregularities, so we
   fall back to left-aligned text below 600px. */
.bio p {
  text-align: justify;
  hyphens: auto;
  -webkit-hyphens: auto;
  text-wrap: pretty;
}
@media (max-width: 600px) {
  .bio p {
    text-align: left;
    hyphens: manual;
    -webkit-hyphens: manual;
  }
}

/* Research projects — list-style layout that mirrors the publications
   page: an em-dash bullet on the left, the project name and tag on
   the same line (tag pushed to the right), and the summary/funding
   stacked beneath. Visually consistent with .pub-list on /publications/. */
.project-list {
  list-style: none;
  padding-left: 0;
  margin: 1rem 0 1.5rem;
}
.project-item {
  position: relative;
  padding: 0.6rem 0 0.6rem 1.5rem;
}
.project-item::before {
  content: "—";
  position: absolute;
  left: 0;
  top: 0.6rem;
  /* Match the line-height of .project-header so the em-dash sits on
     the same baseline as the project name (its first visible line). */
  line-height: 1.4;
  color: var(--text-muted);
}
/* Tighter line-height on the project paragraphs: the body's 1.65 makes
   short single-line paragraphs look too airy; 1.4 reads closer to a
   compact data record. */
.project-header,
.project-summary,
.project-funding {
  line-height: 1.4;
}
.project-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 0.6rem;
  margin: 0;
}
.project-name {
  font-weight: 700;
}
.project-role {
  color: var(--text-muted);
  font-size: var(--text-sm);
}
.project-summary {
  margin: 0.15rem 0 0;
}
.project-funding {
  margin: 0.05rem 0 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* Tags — discreet, lowercase/title-case (no uppercase transform). All
   variants share the same neutral palette; differentiation by text
   alone, not by color. */
.tag {
  display: inline-block;
  font-size: var(--text-xs);
  font-weight: 500;
  padding: 0.1rem 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--bg-soft);
  color: var(--text-muted);
  border: 1px solid var(--border);
}
/* Color-coded variants. Background, text and border use brand-tinted
   palettes for semantic distinction. */
.tag-journal {
  background: var(--tint-blue-soft);
  color: var(--ufrgs-blue);
  border-color: var(--tint-blue-border);
}
.tag-conference {
  background: var(--tint-green-soft);
  color: var(--accent-green);
  border-color: var(--tint-green-border);
}
.tag-book,
.tag-chapter,
.tag-lang-en,
.tag-lang-pt {
  background: var(--bg-soft);
  color: var(--text-muted);
  border-color: var(--border);
}

/* Publications */
.pub-list {
  list-style: none;
  padding-left: 0;
}
.pub-item {
  position: relative;
  padding: 0.6rem 0 0.6rem 1.5rem;
}
.pub-item::before {
  content: "—";
  position: absolute;
  left: 0;
  top: 0.6rem;
  color: var(--text-muted);
}

/* Responsive — two-stage breakpoints:
   1) ≤960px: switch the nav from inline-desktop to hamburger-mobile.
              The desktop nav has 6 long items (~792px including gaps)
              that don't fit alongside the brand at narrower viewports.
              At this width the body typography is still desktop-sized.
   2) ≤860px: compact type scale, hero stacks vertically, brand visual
              shrinks. Page becomes truly "mobile". */

/* === Nav collapse — JS-driven (class-based) ===
   Replaces a fixed pixel breakpoint with a measurement-based switch:
   the script in partials/header.njk measures the header's available
   width vs the nav's natural width on every resize and toggles
   `body.nav-collapsed` accordingly. This way the hamburger appears
   exactly when the inline nav would overflow, regardless of nav text
   length, font loading, or scrollbar presence. */
body.nav-collapsed .nav-toggle { display: flex; }
body.nav-collapsed .site-nav-desktop { display: none; }
body.nav-collapsed .site-nav-mobile {
  display: block;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.25s ease;
  background: var(--surface);
  border-top: 1px solid var(--border);
}
body.nav-collapsed .site-nav-mobile.is-open { max-height: 500px; }
/* Outer mobile nav <ul> only — the :not() guard keeps these styles from
   reaching down into the nested .theme-menu-dropdown <ul>. Without it,
   `display: flex` here was overriding `.theme-menu-dropdown[hidden]
   { display: none }` (specificity 0,2,2 vs 0,2,0), making the theme
   dropdown render expanded all the time in hamburger mode regardless of
   the [hidden] attribute. The padding here would also clobber the
   dropdown's own padding-left indent. */
body.nav-collapsed .site-nav-mobile ul:not(.theme-menu-dropdown) {
  list-style: none;
  flex-direction: column;
  /* Reset align-items inherited from `.site-nav ul:not(.theme-menu-dropdown)`
     above — that rule's `align-items: center` is what the desktop horizontal
     row needs (centring items vertically), but in this column layout it
     becomes horizontal centring, shrinking each <li> to its content width
     and visually centring the menu. `stretch` (the default flex behaviour)
     restores full-width rows so the link/trigger boxes fill the menu and
     their text reads left-aligned. */
  align-items: stretch;
  gap: 0;
  padding: 0.5rem 0 0.75rem;
  margin: 0;
  display: flex;
}
body.nav-collapsed .site-nav a {
  display: block;
  /* Symmetric horizontal extension: the link's box reaches 12px beyond
     each side of the text/container content area. margin pulls the box
     outward; padding pushes the text back to its natural position. The
     hover/active background tint then covers a band that's equidistant
     from both edges of the menu, and the red current-page bar (at
     left: 0 of the new box) sits on the left edge of that band. */
  margin: 0 -0.75rem;
  padding: 0.65rem 0.75rem;
  border-bottom: none;
  position: relative; /* anchor for the active-page indicator below */
  transition: background-color 0.12s ease-out;
}
body.nav-collapsed .site-nav a::after { display: none; }
/* Hover (desktop-narrow) and :active (touch tap) feedback — subtle gray
   tint, transient. Distinct from the persistent current-page indicator
   (the red vertical bar set via [aria-current="page"]::before below). */
body.nav-collapsed .site-nav a:hover,
body.nav-collapsed .site-nav a:active {
  background-color: rgba(0, 0, 0, 0.04);
}
/* Active-page indicator: a thin red vertical bar that sits in the container's
   left padding area, so the active item itself doesn't shift right and has no
   background tint — just a clean visual marker on the left edge that "moves"
   to whichever item is current as you navigate between pages. */
body.nav-collapsed .site-nav a[aria-current="page"]::before {
  content: "";
  position: absolute;
  /* The link's box was extended 12px to the left via negative margin
     (see above), so the bar's left edge now sits at left: 0 of the
     extended box — visually 12px to the left of the text, same as before. */
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--inf-red);
}

/* === Mobile-page breakpoint (860px) === */
@media (max-width: 860px) {
  /* Compact the type scale on mobile. Brings body and lead closer to the
     iOS/Material conventions (16-17px) while preserving the heading hierarchy.
     Variables that flow through to multiple elements (body, .lead, h4) are
     overridden here in one place; h1/h2/h3 keep their existing explicit
     pixel overrides below. */
  :root {
    --text-base: 1.0625rem;  /* 17px (was 19px) — body */
    --text-md:   1.1875rem;  /* 19px (was 21px) — lead, h4 */
    --text-lg:   1.3125rem;  /* 21px (was 23px) — used downstream where applicable */
  }
  body { font-size: var(--text-base); }
  h1 { font-size: 1.75rem; }  /* 28px on mobile */
  h2 { font-size: 1.5rem; }   /* 24px on mobile */
  h3 { font-size: 1.25rem; }  /* 20px on mobile */
  /* Hero stacks on mobile; center the photo and the text block under it.
     Drop the hero's top padding so the gap between the nav and the photo
     matches the gap between the photo and the heading (both ≈ 2rem,
     coming respectively from main.container's padding-top and from the
     hero-grid's row gap). */
  .hero { padding-top: 0; }
  .hero-grid { grid-template-columns: 1fr; text-align: center; }
  /* Visual compensation: the h1 line-box adds ~8px of "phantom" space
     above the cap-height of "L" (line-height 1.25 over a 28px font),
     which makes the photo→name gap appear larger than the nav→photo
     gap. Pull the h1 up by ~8px so the visible spacing is symmetric. */
  .hero h1 { margin-top: -0.5rem; }
  .hero-photo .photo-placeholder { width: 140px; height: 140px; font-size: 2rem; margin: 0 auto; }
  .profile-photo { width: 160px; margin: 0 auto; }
  /* Phone row: when the hero stacks, drop the side-by-side layout and
     stack the phone line and the Lattes/ORCID line vertically (centered),
     so the profile links sit on their own line below the phone. */
  .phone-row {
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 0.4rem;
  }

  .brand-logo { height: 44px; }
}
