/* ================================================================
   bwah.dev — main stylesheet (css/index.css)
   ================================================================
   Loaded on every page via <link rel="stylesheet" href="/assets/css/index.css">.
   Theme colours (--base, --mantle, --text, --mauve, --accent-rgb, etc.)
   come from the Catppuccin flavour files imported below and are swapped
   at runtime by theme-switcher.js via a [data-flavor] attribute on <html>.

   Sections:
     1. Universal   – layout shared by every page (shell, nav, hub header,
                       link buttons, badges, 88x31 wall)
     2. /coffee      – message-card pattern, also reused by /cv and 404
     3. /dev-info    – tech-icon badges
     4. /hardware    – parts-list table
     5. /cv          – terminal-style blinking cursor
     6. /guestbook   – misc guestbook bits
     7. /projects

    CSS WRITTEN BY ME, REFACTORED AND DOCUMENTED WITH CLAUDE
   ================================================================ */

/* IMPORTS */
@import url(fonts.css);            /* @font-face: Comic Code */
@import url(picker.css);           /* theme flavour picker widget */
@import url(discord.css);          /* Discord presence card (/socials) */
@import url(theme-switcher.css);   /* flavour switcher UI */
@import url(themes/frappe.css);    /* Catppuccin Frappé variables */
@import url(themes/latte.css);     /* Catppuccin Latte variables */
@import url(themes/macchiato.css); /* Catppuccin Macchiato variables */
@import url(themes/mocha.css);     /* Catppuccin Mocha variables (default) */

:root {
  /* Shared page backdrop, used on both <html> and <body> so there's no
     flash of unstyled colour on overscroll/rubber-banding. */
  --page-gradient: linear-gradient(135deg, var(--base) 0%, var(--mantle) 60%, var(--crust) 100%);
  /* Shared drop shadow for the message-card / yuri-card pattern (see §2). */
  --card-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
}

/* ================================================================
   1. UNIVERSAL — shared page shell
   ================================================================ */


/* thank you to https://css-tricks.com/snippets/css/typewriter-effect/ for this code lol*/
.typewriter h1 {
  overflow: hidden; /* Ensures the content is not revealed until the animation */
  border-right: .15em solid orange; /* The typwriter cursor */
  white-space: nowrap; /* Keeps the content on a single line */
  margin: 0 auto; /* Gives that scrolling effect as the typing happens */
  letter-spacing: .15em; /* Adjust as needed */
  animation: 
    typing 3.5s steps(40, end),
    blink-caret .75s step-end infinite;
}

/* The typing effect */
@keyframes typing {
  from { width: 0 }
  to { width: 100% }
}

/* The typewriter cursor effect */
@keyframes blink-caret {
  from, to { border-color: transparent }
  50% { border-color: orange; }
}

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

html {
  height: 100%;
  background: var(--page-gradient);
}

body {
  margin: 0;
  min-height: 100vh;
  width: 100%;
  max-width: 100vw;
  overflow-x: hidden;
  font-family: 'Comic Code', sans-serif;
  background: var(--page-gradient);
  color: var(--text);

  /* top-anchored, horizontally centered, scrollable */
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: clamp(2.5rem, 9vh, 6rem) 1.25rem 5rem;
}

/* The single centred content column used on every "hub" style page
   (index, hardware, dev-info, socials, guestbook, ...).
   `.hub` doesn't appear directly in the current markup but is kept as
   a hook for pages that wrap content in a div rather than <main class="intro">. */
main.intro,
.hub {
  width: 100%;
  max-width: 480px;
  z-index: 1;
}

/* --- Hub header: avatar, name, pronouns, tagline (index, hardware, dev-info) --- */

/* <img class="pfp" src="assets/media/avatar.webp" ...> */
.pfp {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  object-fit: cover;
  border: 3px solid var(--mauve);
  box-shadow: 0 4px 18px rgba(var(--accent-rgb), 0.25);
  margin: 10px 0;
}

/* <header class="hub-header"> wrapping the avatar + name + pronouns + intro text */
.hub-header {
  text-align: center;
  margin: 10px 0;
}

.hub-header h1 {
  margin: 0;
  font-size: 2rem;
  font-weight: 700;
  transition: color 0.6s ease;
}

/* "Hi, I'm" part of <h1 class="hey">Hi, I'm<span class="name">Ari</span></h1> */
.hey {
  color: var(--text);
  /* Only lands where .hey is used outside a .hub-header (i.e. /socials);
     inside one, .hub-header h1's `margin: 0` is more specific and wins. */
  margin: 10px 0;
}

/* Name span inside .hey, coloured with the active theme's accent colour */
.name {
  color: rgb(var(--accent-rgb));
}

/* Adds the space between "I'm" and the name, since there's no space
   in the markup between the text node and the <span class="name"> */
.name::before {
  content: " ";
}

/* Small uppercase tagline text, currently only used on /guestbook's header */
.tagline {
  margin: 0.35rem 0 0;
  color: var(--subtext-0);
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* <h2 class="pronouns">(She/They)</h2> under the name */
.pronouns {
  margin: 10px 0;
  color: var(--mauve);
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  text-transform: lowercase;
}

/* Body copy under a hub header, e.g. the "17 year old developer..." blurb.
   Default: a normal centred block that never exceeds its container. */
.intro-text {
  max-width: min(64ch, calc(100vw - 2.5rem));
  margin: 10px auto;
  line-height: 1.6;
  text-align: center;
  color: var(--subtext-1);
  font-size: 0.98rem;
  padding: 0 0.25rem;
}

/* Only inside .hub-header does the blurb need to escape the 480px hub column —
   at column width that long homepage paragraph stacks into a narrow ribbon.
   margin-left:50% puts the block's left edge on the column's centre line and the
   translate pulls it back by half its own width, so it stays centred there at
   any width; the button below keeps the 480px column width.
   Scoped deliberately: /socials puts .intro-text in .presence-stage instead, and
   centring on THAT parent threw it ~156px off-axis. */
.hub-header .intro-text {
  width: min(64ch, calc(100vw - 2.5rem));
  max-width: none;
  margin-left: 50%;
  margin-right: 0;
  transform: translateX(-50%);
}
 ================================================================
   bwah.dev — main stylesheet (css/index.css)
   ================================================================
   Loaded on every page via <link rel="stylesheet" href="/assets/css/index.css">.
   Theme colours (--base, --mantle, --text, --mauve, --accent-rgb, etc.)
   come from the Catppuccin flavour files imported below and are swapped
   at runtime by theme-switcher.js via a [data-flavor] attribute on <html>.

   Sections:
     1. Universal   – layout shared by every page (shell, nav, hub header,
                       link buttons, badges, 88x31 wall)
     2. /coffee      – message-card pattern, also reused by /cv and 404
     3. /dev-info    – tech-icon badges
     4. /hardware    – parts-list table
     5. /cv          – terminal-style blinking cursor
     6. /guestbook   – misc guestbook bits
     7. /projects

    CSS WRITTEN BY ME, REFACTORED AND DOCUMENTED WITH CLAUDE
   ================================================================ */

/* IMPORTS */
@import url(fonts.css);            /* @font-face: Comic Code */
@import url(picker.css);           /* theme flavour picker widget */
@import url(discord.css);          /* Discord presence card (/socials) */
@import url(theme-switcher.css);   /* flavour switcher UI */
@import url(themes/frappe.css);    /* Catppuccin Frappé variables */
@import url(themes/latte.css);     /* Catppuccin Latte variables */
@import url(themes/macchiato.css); /* Catppuccin Macchiato variables */
@import url(themes/mocha.css);     /* Catppuccin Mocha variables (default) */

:root {
  /* Shared page backdrop, used on both <html> and <body> so there's no
     flash of unstyled colour on overscroll/rubber-banding. */
  --page-gradient: linear-gradient(135deg, var(--base) 0%, var(--mantle) 60%, var(--crust) 100%);
  /* Shared drop shadow for the message-card / yuri-card pattern (see §2). */
  --card-shadow: 0 20px 60px rgba(0, 0, 0, 0.45);
}

/* ================================================================
   1. UNIVERSAL — shared page shell
   ================================================================ */


/* thank you to https://css-tricks.com/snippets/css/typewriter-effect/ for this code lol*/
.typewriter h1 {
  overflow: hidden; /* Ensures the content is not revealed until the animation */
  border-right: .15em solid orange; /* The typwriter cursor */
  white-space: nowrap; /* Keeps the content on a single line */
  margin: 0 auto; /* Gives that scrolling effect as the typing happens */
  letter-spacing: .15em; /* Adjust as needed */
  animation: 
    typing 3.5s steps(40, end),
    blink-caret .75s step-end infinite;
}

/* The typing effect */
@keyframes typing {
  from { width: 0 }
  to { width: 100% }
}

/* The typewriter cursor effect */
@keyframes blink-caret {
  from, to { border-color: transparent }
  50% { border-color: orange; }
}

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

html {
  height: 100%;
  background: var(--page-gradient);
}

body {
  margin: 0;
  min-height: 100vh;
  width: 100%;
  max-width: 100vw;
  overflow-x: hidden;
  font-family: 'Comic Code', sans-serif;
  background: var(--page-gradient);
  color: var(--text);

  /* top-anchored, horizontally centered, scrollable */
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: clamp(2.5rem, 9vh, 6rem) 1.25rem 5rem;
}

/* The single centred content column used on every "hub" style page
   (index, hardware, dev-info, socials, guestbook, ...).
   `.hub` doesn't appear directly in the current markup but is kept as
   a hook for pages that wrap content in a div rather than <main class="intro">. */
main.intro,
.hub {
  width: 100%;
  max-width: 480px;
  z-index: 1;
}

/* --- Hub header: avatar, name, pronouns, tagline (index, hardware, dev-info) --- */

/* <img class="pfp" src="assets/media/avatar.webp" ...> */
.pfp {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  object-fit: cover;
  border: 3px solid var(--mauve);
  box-shadow: 0 4px 18px rgba(var(--accent-rgb), 0.25);
  margin: 10px 0;
}

/* <header class="hub-header"> wrapping the avatar + name + pronouns + intro text */
.hub-header {
  text-align: center;
  margin: 10px 0;
}

.hub-header h1 {
  margin: 0;
  font-size: 2rem;
  font-weight: 700;
  transition: color 0.6s ease;
}

/* "Hi, I'm" part of <h1 class="hey">Hi, I'm<span class="name">Ari</span></h1> */
.hey {
  color: var(--text);
  /* Only lands where .hey is used outside a .hub-header (i.e. /socials);
     inside one, .hub-header h1's `margin: 0` is more specific and wins. */
  margin: 10px 0;
}

/* Name span inside .hey, coloured with the active theme's accent colour */
.name {
  color: rgb(var(--accent-rgb));
}

/* Adds the space between "I'm" and the name, since there's no space
   in the markup between the text node and the <span class="name"> */
.name::before {
  content: " ";
}

/* Small uppercase tagline text, currently only used on /guestbook's header */
.tagline {
  margin: 0.35rem 0 0;
  color: var(--subtext-0);
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* <h2 class="pronouns">(She/They)</h2> under the name */
.pronouns {
  margin: 10px 0;
  color: var(--mauve);
  font-size: 0.95rem;
  letter-spacing: 0.04em;
  text-transform: lowercase;
}

/* Body copy under the hub header, e.g. the "17 year old developer..." blurb.
   Deliberately breaks out of the 480px hub column — the blurb is long enough
   that at column width it stacks into a narrow ribbon. `margin-left: 50%` puts
   the block's left edge on the column's centre line, and the translate pulls it
   back by half its own width, so it stays centred on that line at any width.
   The button below is untouched and keeps the 480px column width. */
.intro-text {
  width: min(64ch, calc(100vw - 2.5rem));
  margin-left: 50%;
  transform: translateX(-50%);
  margin-top: 10px;
  margin-bottom: 10px;
  line-height: 1.6;
  color: var(--subtext-1);
  font-size: 0.98rem;
  padding: 0 0.25rem;
}

/* --- Fixed decorative background layers (index.css only touches ::before/::after
       on <body>, not real elements — purely visual, no markup involved) --- */

/* Faint full-bleed background texture image, tinted to match the theme via
   the invert/sepia/hue-rotate filter chain. */
/* body::before {
  content: "";
  position: fixed;
  inset: 0;
  background: url(/assets/theme/peakpx.jpg) center / cover no-repeat;
  filter: invert(86%) sepia(8%) saturate(900%) hue-rotate(190deg) brightness(105%);
  opacity: 0.05;
  pointer-events: none;
  z-index: 0;
} */

/* Vanta writes `position: absolute; top: 0` straight onto its canvas as an
   inline style, and sizes it from body.offsetHeight — which `html {height:100%}`
   pins to one viewport. So on any page long enough to scroll (/hardware,
   /projects, /dev-info) the background stayed at the top and scrolled away,
   leaving the rest of the page bare. Pinning it to the viewport fixes both at
   once: it can't scroll off, and one screen of canvas is all it ever needs.
   !important is required to beat an inline style — author !important is the one
   thing that outranks the style attribute. */
.vanta-canvas {
  position: fixed !important;
}

/* Teto chibi tucked into the bottom-right corner, on every page */
body::after {
  content: "";
  position: fixed;
  right: 0.5rem;
  bottom: 0.5rem;
  width: clamp(96px, 14vw, 168px);
  aspect-ratio: 564 / 547;
  background: url(/assets/theme/teto.png) center / contain no-repeat;
  opacity: 0.18;
  pointer-events: none;
  z-index: 0;
}

/* --- Small status badges, bottom-right stack (not currently used in the
       markup checked, but kept for whatever page renders them) --- */
.badges {
  position: fixed;
  right: 1rem;
  bottom: 1rem;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.4rem;
  pointer-events: none;
  z-index: 5;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  background: var(--surface-0);
  border: 1px solid rgba(var(--accent-rgb), 0.45);
  color: var(--subtext-1);
  font-size: 0.75rem;
  white-space: nowrap;
  transition:
    border-color 0.6s ease,
    box-shadow 0.6s ease;
}

.badge:hover {
  border-color: rgb(var(--accent-rgb));
  box-shadow: 0 4px 14px -6px rgba(var(--accent-rgb), 0.5);
}

.badge-icon {
  width: 15px;
  height: 15px;
  filter: invert(78%) sepia(36%) saturate(640%) hue-rotate(280deg) brightness(105%);
}

/* --- Link / social buttons, e.g. .gitgay-link "Check out my Github" on
       index, and the Twitter/Steam/SkyCrypt buttons on /socials --- */

/* Shared sizing for the button + presence card row on /socials.
   #discord is the empty <div id="discord"> that discord.js fills with a
   .presence-card; .links-socials is the column of .gitgay-link buttons. */
.links-socials {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

#discord,
.links-socials {
  width: 60vw;
  max-width: 800px;
}

/* Wide screens (16:9 and wider) */
@media (min-aspect-ratio: 16/9) {
  #discord,
  .links-socials {
    width: 40vw;
    max-width: 600px;
  }
}

/* Pill-shaped call-to-action button, e.g. <a class="gitgay-link">Check out my Github</a> */
.gitgay-link {
  display: block;
  width: 100%;
  background-color: var(--mauve);
  border: none;
  /* --base rather than a literal, so the label inverts with the flavour:
     dark text on mocha's light mauve, light text on latte's dark mauve. */
  color: var(--base);
  padding: 0.85rem 1.5rem;
  text-align: center;
  text-decoration: none;
  font-size: 1rem;
  font-weight: 600;
  margin: 10px 2px;
  max-width: 100%;
  cursor: pointer;
  border-radius: 14px;
  font-family: "Comic Code";
  transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
  box-shadow: 0 6px 18px -8px rgba(var(--accent-rgb), 0.7);
}

/* .presence-card is generated by js/discord.js inside #discord; kept in
   step with .gitgay-link's full-width sizing so the button column and the
   Discord card line up. */
.presence-stage .presence-card {
  max-width: 100%;
}

.gitgay-link:hover {
  transform: translateY(-2px);
  filter: brightness(1.05);
  box-shadow: 0 10px 24px -8px rgba(var(--accent-rgb), 0.8);
}

.gitgay-link:active {
  transform: translateY(0);
}

/* --- 88x31 button wall, bottom-centre, index page only ---
   <aside class="button-wall"><a><img></a>...</aside> */
.button-wall {
  position: fixed;
  bottom: 1rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  max-width: min(90vw, 480px);
}

.button-wall a {
  display: block;
  line-height: 0;
}

.button-wall img {
  width: 88px;
  height: 31px;
  image-rendering: pixelated;
  border: 1px solid var(--surface-1);
  border-radius: 2px;
  transition: transform 0.12s ease, border-color 0.12s ease;
}

.button-wall a:hover img {
  transform: translateY(-2px);
  border-color: rgb(var(--accent-rgb));
}

/* --- Slide-out side nav, present on every page ---
   <span class="hamburger" onclick="openNav()">
   <div id="mySidenav" class="sidenav">
     <a class="closebtn" onclick="closeNav()">×</a>
     <a href="/" class="active">Index</a> ...
   </div>
   openNav()/closeNav() (in index.js or similar) toggle .sidenav's width
   between 0 and its open state. */
.sidenav {
  height: 100%;
  width: 0;
  position: fixed;
  z-index: 2000;
  top: 0;
  left: 0;
  background-color: var(--base);
  overflow-x: hidden;
  transition: 0.5s;
  padding-top: 60px;
}

.sidenav a {
  padding: 8px 8px 8px 32px;
  text-decoration: none;
  font-size: 25px;
  color: var(--subtext-0);
  display: block;
  transition: 0.3s;
}

.sidenav a:hover {
  color: var(--text);
}

/* Highlights the link matching the current page, e.g. class="active" on
   the "Index" link when on / */
.sidenav a.active {
  color: var(--lavender);
}

.sidenav .closebtn {
  position: absolute;
  top: 0;
  right: 25px;
  font-size: 36px;
  margin-left: 50px;
}

.hamburger {
  position: fixed;
  top: 1rem;
  left: 1rem;
  font-size: 30px;
  cursor: pointer;
  z-index: 1000;
}

/* ---- Responsive tweaks (grouped by breakpoint) ---- */

@media (max-width: 600px) {
  /* .intro isn't used directly in current markup (main.intro is), but
     kept in case a page nests an inner .intro wrapper. */
  .intro {
    color: var(--text);
    text-align: center;
    padding: 0;
  }


  .hub-header h1 {
    font-size: 1.7rem;
  }

  html,
  body {
    width: 100%;
    height: 100dvh;
  }
}

@media (max-width: 768px) {
  #discord,
  .links-socials {
    width: 90vw;
  }

  /* button wall is desktop-only; hidden on mobile to save vertical space */
  .button-wall {
    display: none;
  }
}

@media screen and (max-height: 450px) {
  /* Short viewports (e.g. landscape phones): shrink the side nav so all
     links fit without scrolling */
  .sidenav {
    padding-top: 15px;
  }

  .sidenav a {
    font-size: 18px;
  }
}

@media (min-width: 768px) {
  /* .desktop-break isn't in the current markup, but is a hook for a <br>
     or spacer that should only render on desktop layouts */
  .desktop-break {
    display: block;
  }
}

/* ================================================================
   2. /coffee — also reused by /cv and 404.html
   ================================================================
   Any "here's a status message" page (HTTP 418 easter egg on /coffee,
   "Coming Soon" on /cv, and the 404 page) shares this pattern:
     <body class="message-page">
       <div class="message-card">
         <div class="glyph">🫖</div>
         <h1 class="warn">418</h1>
         <p>...</p>
         <a class="home-link" href="/">← Back to home</a>
       </div>
     </body>
   ================================================================ */

.teapot {
  font-size: 6vh;
}

/* Centers the .message-card / .yuri-card vertically & horizontally in
   the viewport */
body.message-page {
  display: flex;
  min-height: 100vh;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

/* .message-card (status pages: /coffee, /cv, 404) and .yuri-card
   (/yuri) share every property except max-width, so the shared bits are
   merged here and .yuri-card only overrides what's different. */
.message-card,
.yuri-card {
  background: var(--surface-0);
  padding: 2.5rem;
  border-radius: 12px;
  max-width: 520px;
  width: 100%;
  box-shadow: var(--card-shadow);
  text-align: center;
  line-height: 1.6;
  color: var(--text);
  border: 1px solid var(--surface-1);
}

.yuri-card {
  max-width: 900px;
}

.message-card h1 {
  margin-top: 0;
  color: var(--pink);
  letter-spacing: -0.01em;
}

.message-card p {
  color: var(--subtext-1);
}

/* Status-colour modifiers for the <h1>, e.g. class="warn" on 418/Coming
   Soon/404 headings. .ok / .err exist for green/red variants even though
   only .warn shows up in the current pages. */
.message-card .ok {
  color: var(--green);
}

.message-card .err {
  color: var(--red);
}

.message-card .warn {
  color: var(--yellow);
}

.message-card .muted {
  color: var(--overlay-1);
  font-size: 0.9rem;
  margin-top: 1.5rem;
}

.message-card code {
  background: var(--mantle);
  color: var(--rosewater);
  padding: 0.1rem 0.4rem;
  border-radius: 6px;
  font-size: 0.85em;
}

/* <a class="home-link" href="/">← Back to home</a> */
.message-card a.home-link {
  display: inline-block;
  margin-top: 1.5rem;
  color: var(--pink);
  transition: color 0.15s ease;
}

.message-card a.home-link:hover {
  color: var(--flamingo);
  text-decoration: underline;
}

/* <div class="glyph">🫖</div> — big emoji/icon above the heading */
.message-card .glyph {
  font-size: 2.75rem;
  line-height: 1;
  margin-bottom: 0.5rem;
}

@media (max-width: 540px) {
  .message-card {
    padding: 1.75rem 1.5rem;
  }
}

/* ================================================================
   3. /dev-info — https://github.com/doughmination/clove-is-a-dev/src/branch/main/assets/css/main.css
   ================================================================ */

/* Let only the dev-info page scroll; the link hub on other pages stays
   locked to the viewport (no scrollbar) since its content always fits. */
html:has(.dev-info),
body:has(.dev-info) {
  height: auto;
  min-height: 100dvh;
  overflow-y: auto;
}

body:has(.dev-info) .hub {
  max-width: 860px;
}

/* <div class="dev-info"> wraps the grid of tech-icon badges */
.dev-info {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  margin: 0 auto;
  padding-bottom: 4.5rem;
}

/* Simple Icons rendered via CSS mask so colour is theme-driven.
   Markup: <span class="tech-icon pink" style="--si:url('https://cdn.simpleicons.org/SLUG')" aria-label="..."></span> */
.tech-icon {
  position: relative;
  width: 30px;
  height: 30px;
  display: inline-block;
  transition: transform 0.15s ease, filter 0.15s ease;
}

/* The icon shape — masked so the label (::after) stays visible */
.tech-icon::before {
  content: "";
  position: absolute;
  inset: 0;
  background-color: currentColor;
  -webkit-mask: var(--si) center / contain no-repeat;
  mask: var(--si) center / contain no-repeat;
}

.tech-icon:hover {
  transform: translateY(-2px) scale(1.12);
  filter: drop-shadow(0 4px 8px currentColor);
}

/* Hover label — pulls text from the element's aria-label, tinted to
   match the icon's colour */
.tech-icon::after {
  content: attr(aria-label);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  padding: 0.25rem 0.5rem;
  border-radius: 6px;
  background: var(--crust);
  border: 1px solid currentColor;
  color: var(--text);
  font-size: 0.72rem;
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 10;
}

.tech-icon:hover::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* Catppuccin accent colour modifiers — one class per icon, matching the
   `class="tech-icon <colour>"` used in dev-info/index.html (rosewater,
   pink, mauve, red, maroon, peach, yellow, green, teal, sky, sapphire,
   blue, lavender). Values pull from the active theme's variables so
   icons re-tint automatically when the flavour is switched. */
.tech-icon.rosewater { color: var(--rosewater); }
.tech-icon.pink      { color: var(--pink); }
.tech-icon.mauve     { color: var(--mauve); }
.tech-icon.red       { color: var(--red); }
.tech-icon.maroon    { color: var(--maroon); }
.tech-icon.peach     { color: var(--peach); }
.tech-icon.yellow    { color: var(--yellow); }
.tech-icon.green     { color: var(--green); }
.tech-icon.teal      { color: var(--teal); }
.tech-icon.sky       { color: var(--sky); }
.tech-icon.sapphire  { color: var(--saphire); } /* NB: var name is --saphire (typo) in the theme files, not --sapphire */
.tech-icon.blue      { color: var(--blue); }
.tech-icon.lavender  { color: var(--lavender); }

.hardware-list {
  text-align: left;
}

/* ================================================================
   4. /hardware — PC parts list
   ================================================================
   Markup (hardware/index.html):
     <div class="pcpp-part-list">
       <div class="pcpp-row">
         <div class="pcpp-type">CPU</div>
         <div class="pcpp-item"><a>...</a></div>
       </div>
       ...
     </div>
   ================================================================ */

/* NOTE: originally these rules were nested *inside* .pcpp-part-list
   using CSS nesting (`.pcpp-part-list { .pcpp-part-list { ... } }`),
   which meant .pcpp-row/.pcpp-type/.pcpp-item only matched elements
   inside a *second*, inner .pcpp-part-list — but the markup only ever
   has one. Flattened to plain top-level selectors so they apply to the
   actual list/rows as used on the page. */
.pcpp-part-list {
  width: 100%;
  max-width: 700px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 14px; /* the "bigger gap between entries" */
}

.pcpp-row {
  display: grid;
  grid-template-columns: 160px 1fr;
  align-items: center;
  column-gap: 24px;
}

.pcpp-type {
  text-align: right;
  font-weight: 600;
  color: var(--subtext-1);
  white-space: nowrap;
}

.pcpp-item {
  text-align: left;
  word-break: keep-all;
}

.pcpp-item a {
  text-decoration: none;
  color: var(--text);
}

.pcpp-item a:hover {
  text-decoration: underline;
}

/* ================================================================
   5. /cv
   ================================================================ */

/* Blinking terminal-style cursor, appended after an element via ::after */
.cursor::after {
  content: '';
  display: inline-block;
  width: 10px;
  height: 1em;
  background: var(--green);
  animation: blink 1s step-end infinite;
  vertical-align: bottom;
  margin-left: 5px;
}

@keyframes blink {
  50% {
    opacity: 0;
  }
}

/* ================================================================
   6. /guestbook
   ================================================================ */

/* Signature/attribution text style, e.g. "— clove" under a guestbook entry */
.clove {
  opacity: 50%;
  color: var(--subtext-1);
}

/* ================================================================
   7. /projects
   ================================================================ */

/* A .project row is an image and a text block side by side, which doesn't fit
   the 480px hub column every other page uses: 70% of 480 left the text column
   at ~99px and pushed the thumbnail past the card edge (clipped by the
   overflow:hidden below). Widened here only, via :has(), so no other page
   moves — same approach as the /dev-info block above. */
body:has(.projects) main.intro {
  max-width: 900px;
}

.project {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 2rem;

    width: 70%;
    max-width: 600px;
    margin: 2rem auto;
    padding: 1.5rem;

    border: 1px solid var(--surface-2);
    border-radius: 8px;
    background-color: var(--surface-0);

    position: relative;
    overflow: hidden;

    /* The card element itself is the <a> (see projects/index.html), so pin the
       body copy back to normal text — otherwise every line inherits the link
       colour and the whole tile reads as one blue block. */
    color: var(--text);
    text-decoration: none;
}

/* The title carries the "this is a link" colour now that the inner <a> is gone.
   --lavender because it's the closest palette entry to the browser default link
   blue this used to render as, and unlike that default it follows the flavour. */
.project h3 {
    color: var(--lavender);
}

.project:hover {
    background-color: var(--surface-1);
}

/* The card is a big keyboard target now; make the focus ring obvious. */
.project:focus-visible {
    outline: 2px solid var(--pink);
    outline-offset: 2px;
}

.project-reverse {
    flex-direction: row-reverse;
}

@media (max-width: 768px) {
    .project {
        flex-direction: column;
        margin: 2rem 1rem;
        text-align: center;
    }

    .project-reverse {
        flex-direction: column;
    }
}

/* NOTE: this one is global, not scoped to /projects — it strips the underline
   from every plain <a> on the site. Left as-is because it isn't breaking
   anything, but move it under .projects if you only meant it for this page.
   (A bare `header {}` rule used to sit here too. /projects has no <header> at
   all, while .hub-header on /, /hardware, /dev-info and /socials *is* one, so
   it was silently adding 96px 32px 80px of padding to those pages and shoving
   the visitor counter into the intro text. Removed.) */
a {
    text-decoration: none;
}

/* The image column is bounded here rather than on the <img>, so how much room
   it takes doesn't depend on the source file's intrinsic size. flex: 0 0 auto
   keeps it from being shrunk by a long description. */
.project-image {
    flex: 0 0 auto;
    max-width: 240px;
}

/* Bounds the box, never the shape. width/height stay auto and only maxima are
   applied, so every thumbnail keeps its own aspect ratio — 16:9 screenshots,
   square logos and the is-a.stupid.cat SVG all scale to fit rather than being
   stretched to a fixed size the way the old inline width/height did. */
.project-thumbnail {
    display: block;
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 120px;
}

.projects {
    padding: 5rem 1rem;
    text-align: center;
}

ul {
    padding-left: 0;
    list-style-position: inside;
}

.about-text {
    padding-left: 19rem;
    padding-right: 19rem;
}

