/* ==========================================================================
   styles.css - Single stylesheet for the whole site.

   Structure of this file:
     1.  Design tokens (CSS custom properties: colours, spacing, fonts)
     2.  Global resets and base element styles
     3.  Accessibility helpers (skip link, focus rings)
     4.  Layout helpers (page shell, sections)
     5.  Navigation bar (desktop + mobile burger menu)
     6.  Hero / banner image block
     7.  Cards (University Projects grid + Awards panel)
     8.  Sub-page content (article text, video, images)
     9.  Footer
     10. Responsive breakpoints
     11. Motion / contrast / print preferences

   To re-skin the site you should only need to change the values in
   section 1 - everything else is built from those tokens.
   ========================================================================== */


/* ==========================================================================
   1. DESIGN TOKENS
   Change these variables to restyle the entire site at once.
   ========================================================================== */
:root {
  /* --- Dark blue / futuristic colour scheme --- */
  --c-bg-deep:      #03071a;   /* Deepest background (page edges)            */
  --c-bg:           #060d24;   /* Main page background                       */
  --c-surface:      #0c1633;   /* Panels and cards                           */
  --c-surface-hi:   #121f45;   /* Card hover / raised surfaces               */
  --c-border:       #1c2f5e;   /* Subtle borders between surfaces            */
  --c-border-hi:    #2f4d94;   /* Border on hover / focus                    */

  --c-accent:       #38bdf8;   /* Primary accent - cyan                      */
  --c-accent-soft:  #7dd3fc;   /* Lighter accent for hover states            */
  --c-accent-2:     #8b5cf6;   /* Secondary accent - violet (for gradients)  */

  --c-text:         #e8eeff;   /* Main body text                             */
  --c-text-muted:   #9db0d4;   /* Secondary / supporting text                */
  --c-text-dim:     #6f83ab;   /* Captions, labels, footer small print       */

  /* --- Typography ---
     A system font stack is used on purpose: it needs no external font
     download, which keeps the site fast and avoids third-party requests.
     This also lets the Content-Security-Policy stay strict (see server.js). */
  --font-body: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
  --font-head: "Segoe UI Semibold", "Segoe UI", system-ui, -apple-system, Arial, sans-serif;
  --font-mono: "Cascadia Code", Consolas, "SF Mono", "Roboto Mono", monospace;

  /* --- Spacing scale (keeps vertical rhythm consistent) --- */
  --space-xs:  0.5rem;
  --space-sm:  0.875rem;
  --space-md:  1.5rem;
  --space-lg:  2.5rem;
  --space-xl:  4rem;
  --space-2xl: 6rem;

  /* --- Shape and depth --- */
  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 20px;   /* The "squares with rounded edges" on the home page */
  --radius-pill: 999px;

  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.35);
  --shadow-md: 0 10px 30px rgba(0, 0, 0, 0.45);
  --shadow-glow: 0 0 0 1px rgba(56, 189, 248, 0.35),
                 0 12px 40px rgba(56, 189, 248, 0.14);

  /* --- Layout --- */
  --max-width: 1120px;   /* Maximum content width on large screens */
  --nav-height: 68px;

  /* --- Home-page banner image height ---------------------------------
     The banner is capped so that it fills most of the first screen but
     always stops short of it, leaving the first lines of the intro
     paragraph visible without scrolling.

     The cap is worked out by subtracting what is already using vertical
     space, rather than being a flat percentage of the screen - a flat
     percentage ignores the nav bar and heading above the image, which is
     what made the image reach the bottom of the screen.

     The photo is never stretched to meet this height: it is scaled to fit
     inside it, so its aspect ratio is untouched either way. */

  /* Vertical space above the banner that is already spoken for: the sticky
     nav bar (68), the hero's top padding (24), the "Patrick Mills" heading
     (~46-66 depending on screen width), the gap below it (24), and the
     banner frame's own padding and border (22). */
  --hero-space-above: 200px;

  /* Space deliberately kept free BELOW the banner, so the first couple of
     lines of the intro paragraph are on screen without scrolling.
     Increase this to reveal more text; decrease it for a taller image. */
  --hero-text-peek: 80px;

  /* Tallest the Awards panel image may be. It is shown whole (never
     cropped) and never enlarged beyond its natural size, so this only
     matters for an image taller than this value. */
  --award-img-max-h: 220px;

  /* Floor and ceiling for the banner height. The floor stops it becoming a
     postage stamp on a very short window; the ceiling stops it dominating
     a very tall screen. */
  --hero-banner-min-h: 180px;
  --hero-banner-cap:   640px;

  /* clamp(smallest, preferred, largest) */
  --hero-banner-max-h: clamp(
    var(--hero-banner-min-h),
    calc(100vh - var(--hero-space-above) - var(--hero-text-peek)),
    var(--hero-banner-cap));

  /* Same cap in svh ("small viewport height"), which is the height a phone
     has while its address bar IS on screen. That is the worst case, so
     sizing to it guarantees the text still shows on first load. Plain vh
     on a phone means the full height with the bar hidden, which would put
     the text back off the bottom. Used via @supports in section 6. */
  --hero-banner-max-h-svh: clamp(
    var(--hero-banner-min-h),
    calc(100svh - var(--hero-space-above) - var(--hero-text-peek)),
    var(--hero-banner-cap));

  /* --- Motion --- */
  --transition: 200ms cubic-bezier(0.4, 0, 0.2, 1);
}


/* ==========================================================================
   2. GLOBAL RESET AND BASE STYLES
   ========================================================================== */

/* border-box makes width calculations predictable: padding no longer adds
   to the declared width of a box. */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* Smooth scrolling for in-page anchor links. Disabled further down for
     visitors who ask their OS for reduced motion. */
  scroll-behavior: smooth;
  /* Stops content hiding under the sticky nav bar when jumping to an anchor. */
  scroll-padding-top: calc(var(--nav-height) + 1rem);
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-body);
  /* Fluid font size: 16px on small screens, growing to ~18px on desktop. */
  font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
  line-height: 1.7;
  color: var(--c-text);
  background-color: var(--c-bg);
  /* Layered radial gradients create a subtle "deep space" glow behind the
     content without needing any image files. */
  background-image:
    radial-gradient(ellipse 80% 55% at 50% -10%, rgba(56, 189, 248, 0.16), transparent 60%),
    radial-gradient(ellipse 60% 50% at 90% 8%, rgba(139, 92, 246, 0.12), transparent 65%),
    linear-gradient(180deg, var(--c-bg) 0%, var(--c-bg-deep) 100%);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  /* Prevents a horizontal scrollbar if a decorative element overflows. */
  overflow-x: hidden;
}

/* --- Headings --- */
h1, h2, h3 {
  font-family: var(--font-head);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;   /* Tighter tracking reads as more "designed" */
  margin: 0 0 var(--space-md);
  color: #fff;
}

h1 { font-size: clamp(2.2rem, 1.6rem + 3vw, 3.6rem); }
h2 { font-size: clamp(1.6rem, 1.3rem + 1.6vw, 2.4rem); }
h3 { font-size: clamp(1.15rem, 1.05rem + 0.6vw, 1.4rem); letter-spacing: -0.01em; }

p {
  margin: 0 0 var(--space-md);
  color: var(--c-text-muted);
}

/* --- Links --- */
a {
  color: var(--c-accent);
  text-decoration-color: rgba(56, 189, 248, 0.4);
  text-underline-offset: 3px;
  transition: color var(--transition), text-decoration-color var(--transition);
}
a:hover { color: var(--c-accent-soft); text-decoration-color: currentColor; }

/* --- Media defaults ---
   max-width:100% is the single most important responsive rule: it stops
   any image or video being wider than the box that contains it. */
img,
video {
  max-width: 100%;
  height: auto;
  display: block;
}


/* ==========================================================================
   3. ACCESSIBILITY HELPERS
   ========================================================================== */

/* "Skip to main content" link: visually hidden until it receives keyboard
   focus, then it slides into view. Lets keyboard users bypass the nav. */
.skip-link {
  position: absolute;
  top: -100px;
  left: var(--space-md);
  z-index: 200;
  padding: 0.75rem 1.25rem;
  background: var(--c-accent);
  color: #03071a;
  font-weight: 600;
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  text-decoration: none;
  transition: top var(--transition);
}
.skip-link:focus { top: 0; color: #03071a; }

/* A single, clearly visible focus ring for every interactive element.
   :focus-visible only shows it for keyboard users, not on mouse clicks. */
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 3px;
  border-radius: var(--radius-sm);
}

/* Screen-reader-only text: present in the accessibility tree, invisible
   on screen. Used to add context to icon-only elements. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ==========================================================================
   4. LAYOUT HELPERS
   ========================================================================== */

/* The page is a vertical flex column so the footer is pushed to the bottom
   even on short pages (see .site-main { flex: 1 }). */
.page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

.site-main { flex: 1 1 auto; }

/* Centred, width-limited container with responsive side padding. */
.container {
  width: 100%;
  max-width: var(--max-width);
  margin-inline: auto;
  padding-inline: clamp(1rem, 4vw, 2rem);
}

/* Standard vertical spacing for each section of a page. */
.section { padding-block: var(--space-xl); }
.section--tight { padding-block: var(--space-lg); }

/* Section heading with a small glowing accent bar to its left. */
.section-title {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: var(--space-lg);
}
.section-title::before {
  content: "";
  flex: 0 0 auto;
  width: 4px;
  height: 1.1em;
  border-radius: var(--radius-pill);
  background: linear-gradient(180deg, var(--c-accent), var(--c-accent-2));
  box-shadow: 0 0 12px rgba(56, 189, 248, 0.6);
}

/* Thin gradient divider used between major sections. */
.divider {
  height: 1px;
  border: 0;
  margin: 0;
  background: linear-gradient(90deg, transparent, var(--c-border-hi), transparent);
}


/* ==========================================================================
   5. NAVIGATION BAR
   ========================================================================== */

.site-nav {
  position: sticky;      /* Stays pinned to the top while the page scrolls */
  top: 0;
  z-index: 100;
  background: rgba(6, 13, 36, 0.82);
  /* Frosted-glass effect. Harmless in browsers that do not support it. */
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
  border-bottom: 1px solid var(--c-border);
}

.site-nav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  min-height: var(--nav-height);
}

/* Site name / logo on the left of the nav bar. */
.site-nav__brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-head);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #fff;
  text-decoration: none;
  white-space: nowrap;
}
.site-nav__brand:hover { color: #fff; }

/* The small glowing square holding the initials before the brand name. */
.site-nav__mark {
  width: 28px; height: 28px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  border-radius: 7px;
  background: linear-gradient(135deg, var(--c-accent), var(--c-accent-2));
  color: #03071a;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0;
  box-shadow: 0 0 16px rgba(56, 189, 248, 0.45);
}

/* Burger button - hidden on desktop, shown at the mobile breakpoint. */
.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;   /* 44px = comfortable touch target size */
  padding: 0;
  background: transparent;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-sm);
  color: var(--c-text);
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition);
}
.nav-toggle:hover { border-color: var(--c-border-hi); background: var(--c-surface); }

/* The three burger lines. They animate into an "X" when the menu opens. */
.nav-toggle__bars,
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  display: block;
  width: 20px;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  transition: transform var(--transition), opacity var(--transition);
}
.nav-toggle__bars { position: relative; }
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  content: "";
  position: absolute;
  left: 0;
}
.nav-toggle__bars::before { top: -6px; }
.nav-toggle__bars::after  { top:  6px; }

/* aria-expanded is toggled by main.js. Styling from the ARIA attribute keeps
   the visual state and the accessible state in sync by construction. */
.nav-toggle[aria-expanded="true"] .nav-toggle__bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::before { transform: translateY(6px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::after  { transform: translateY(-6px) rotate(-45deg); }

/* Nav links list */
.site-nav__list {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-nav__link {
  display: block;
  padding: 0.5rem 0.85rem;
  border-radius: var(--radius-pill);
  font-size: 0.95rem;
  color: var(--c-text-muted);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--transition), background var(--transition);
}
.site-nav__link:hover {
  color: #fff;
  background: var(--c-surface-hi);
}

/* The link for the page you are currently on.
   aria-current="page" is set by hand in each HTML file. */
.site-nav__link[aria-current="page"] {
  color: #fff;
  background: linear-gradient(135deg, rgba(56, 189, 248, 0.22), rgba(139, 92, 246, 0.22));
  box-shadow: inset 0 0 0 1px rgba(56, 189, 248, 0.45);
}


/* ==========================================================================
   6. HERO / BANNER BLOCK (home page)
   ========================================================================== */

/* The top padding here is the gap between the navigation bar and the
   "Patrick Mills" heading. Keep it small so the heading sits just under
   the nav; the larger value is the space below the hero. */
.hero { padding-block: var(--space-md) var(--space-lg); }

.hero__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--space-md);
}

/* Small pill above the name, e.g. "Software Engineering Portfolio". */
.eyebrow {
  display: inline-block;
  padding: 0.3rem 0.9rem;
  border: 1px solid var(--c-border-hi);
  border-radius: var(--radius-pill);
  background: rgba(56, 189, 248, 0.08);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--c-accent-soft);
  margin: 0;
}

.hero__title {
  margin: 0;
  /* Gradient text: a gradient background is clipped to the glyph shapes.
     A plain colour is declared first as a fallback for older browsers. */
  color: #fff;
  background: linear-gradient(135deg, #ffffff 20%, var(--c-accent-soft) 70%, var(--c-accent-2) 110%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* --- Hero banner image ---------------------------------------------------
   The banner sits in the same rounded frame as the images on the project
   pages (.media-frame in section 8), so the two match.

   HOW THE SIZING WORKS - and why the photo is never distorted:

   The image has width: auto AND height: auto, with a cap on each. When both
   dimensions are automatic the browser scales the image down to fit inside
   those caps *while keeping its own aspect ratio* - it never stretches one
   side to fill the space. The result:

     - a wide (landscape) photo runs the full width of the page, because the
       width cap is the one it hits first;
     - a tall (portrait) photo stops at --hero-banner-max-h instead of
       growing to a height that would push the text below it off the screen,
       and is centred with the frame hugging it.

   Which cap "wins" therefore depends on the shape of the photo and on the
   size of the visitor's screen, with no distortion either way.

   The frame uses width: fit-content so that the rounded border wraps
   whatever size the image settles at, instead of leaving empty panel
   either side of a narrow photo. */
.hero-banner {
  width: fit-content;
  max-width: 100%;
  margin: 0 auto;                   /* Centres the frame in the hero */
  padding: 10px;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  background: linear-gradient(160deg, var(--c-surface), var(--c-bg-deep));
  box-shadow: var(--shadow-md);
}

.hero-banner__inner {
  overflow: hidden;                 /* Clips the photo to the rounded corners */
  border-radius: var(--radius-md);
  background: var(--c-bg-deep);
  line-height: 0;                   /* Removes the gap under an inline image */
}

.hero-banner__img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;                          /* Never wider than the page   */
  max-height: var(--hero-banner-max-h);     /* Never taller than the cap   */
  margin-inline: auto;
}

/* svh is the viewport height a phone has while its address bar is showing -
   the worst case for fitting things on screen. Sizing to it is what keeps
   the intro text visible on a phone at first load. Applied only where the
   browser understands the unit, so older browsers keep the vh value above. */
@supports (max-height: 1svh) {
  .hero-banner__img { max-height: var(--hero-banner-max-h-svh); }
}

/* Intro paragraph under the banner.

   No max-width, so it runs to the same left and right edges as the project
   cards and the Awards panel further down the page. Everything on the page
   is a child of .container, so removing the cap here is what lines them all
   up. (Put a max-width back - 62ch was the old value - if you ever want a
   narrower, more book-like column of text.) */
.hero__text {
  max-width: none;
  margin: 0 auto;
  /* Left-aligned, overriding the text-align: center that .hero__inner sets
     for the heading and banner above it. Centring reads well for a short
     heading, but across the full content width it gives a long paragraph a
     ragged left edge that the eye has to hunt for on every new line. This
     also matches the body text on the three project pages. */
  text-align: left;
}


/* ==========================================================================
   7. CARDS - University Projects grid and Awards panel
   ========================================================================== */

/* CSS Grid: two equal columns on desktop.
   The responsive rules in section 10 collapse this to one column.
   minmax(0, 1fr) rather than plain 1fr stops long words forcing the
   columns wider than the available space. */
.project-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-md);
}

/* Each project card is one big link, so the whole square is clickable. */
.card {
  display: flex;
  flex-direction: column;
  overflow: hidden;              /* Keeps the image inside the rounded corners */
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  background: linear-gradient(160deg, var(--c-surface), var(--c-bg-deep));
  color: inherit;
  text-decoration: none;
  box-shadow: var(--shadow-sm);
  transition: transform var(--transition), border-color var(--transition),
              box-shadow var(--transition), background var(--transition);
}
.card:hover,
.card:focus-visible {
  transform: translateY(-4px);
  border-color: var(--c-border-hi);
  background: linear-gradient(160deg, var(--c-surface-hi), var(--c-bg-deep));
  box-shadow: var(--shadow-md), 0 0 0 1px rgba(56, 189, 248, 0.3);
  color: inherit;
}

/* Fixed-ratio image area, so the two cards line up perfectly and their
   titles sit at the same height even though the two images are different
   shapes (665x508 and 1705x899 at the time of writing). */
.card__media {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background: var(--c-bg-deep);
  border-bottom: 1px solid var(--c-border);
}

.card__img {
  width: 100%;
  height: 100%;

  /* "contain" scales the picture until it fits ENTIRELY inside the box,
     keeping its proportions - so the whole image is always visible.
     The alternative, "cover", fills the box completely but achieves that
     by cropping whatever will not fit, which is what was cutting the tops
     and sides off these two images.

     The trade-off is that a picture whose shape does not match the 16:10
     box leaves a little empty space along two edges. That space shows the
     card's own dark background, so it reads as a frame rather than a gap.
     Nothing is ever stretched: the aspect ratio is untouched either way. */
  object-fit: contain;

  transition: filter 400ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Hover: brighten rather than zoom. A zoom (the old transform: scale)
   would push the image past the edges of the box and crop it again, which
   is exactly what "contain" is here to avoid. */
.card:hover .card__img { filter: brightness(1.08); }

.card__body {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: var(--space-md);
  flex: 1 1 auto;
}
.card__title {
  margin: 0;
  color: #fff;
  font-size: clamp(1.05rem, 1rem + 0.4vw, 1.3rem);
}
.card__meta {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.78rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--c-text-dim);
}

/* Small "View project" cue pinned to the bottom of a card. */
.card__cue {
  margin-top: auto;
  padding-top: 0.5rem;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--c-accent);
}
.card__cue span { display: inline-block; transition: transform var(--transition); }
.card:hover .card__cue span { transform: translateX(4px); }

/* --- Awards panel: image on the left, text on the right --- */
.award {
  display: grid;
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  align-items: stretch;
  overflow: hidden;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  background: linear-gradient(120deg, var(--c-surface), var(--c-bg-deep) 70%);
  color: inherit;
  text-decoration: none;
  box-shadow: var(--shadow-sm);
  transition: transform var(--transition), border-color var(--transition),
              box-shadow var(--transition);
}
.award:hover,
.award:focus-visible {
  transform: translateY(-4px);
  border-color: var(--c-border-hi);
  box-shadow: var(--shadow-md), 0 0 0 1px rgba(139, 92, 246, 0.35);
  color: inherit;
}

/* The award image is centred in its cell rather than stretched to fill it.
   place-items: center does the centring; the padding keeps the picture off
   the panel's edges. */
.award__media {
  display: grid;
  place-items: center;
  padding: var(--space-sm);
  overflow: hidden;
  background: var(--c-bg-deep);
  border-right: 1px solid var(--c-border);
}

/* width and height are both auto with a cap on each, so the browser scales
   the picture to fit inside the cell while keeping its proportions - the
   whole image is visible and nothing is cropped. Because it is auto rather
   than 100%, a small image is also never blown up past its natural size,
   which would make it blurry. */
.award__img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: var(--award-img-max-h);
  object-fit: contain;
}

.award__body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.4rem;
  padding: var(--space-md);
}
.award__title { margin: 0; color: #fff; }

/* Gold "Winner" badge. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  align-self: flex-start;
  padding: 0.25rem 0.7rem;
  border: 1px solid rgba(250, 204, 21, 0.45);
  border-radius: var(--radius-pill);
  background: rgba(250, 204, 21, 0.1);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #fde68a;
}


/* ==========================================================================
   8. SUB-PAGE CONTENT (project / award detail pages)
   ========================================================================== */

/* Page header block at the top of each sub-page. */
.page-header {
  padding-block: var(--space-lg) var(--space-md);
  border-bottom: 1px solid var(--c-border);
  background: radial-gradient(ellipse 70% 100% at 20% 0%, rgba(56, 189, 248, 0.10), transparent 70%);
}
.page-header__title { margin-bottom: 0.5rem; }
.page-header__meta {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--c-text-dim);
}

/* Breadcrumb "Back to home" link. */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: var(--space-sm);
  font-size: 0.9rem;
  color: var(--c-text-muted);
  text-decoration: none;
}
.back-link:hover { color: var(--c-accent); }

/* Article body.

   No max-width, so the paragraphs run to the same left and right edges as
   the videos, images and tag lists on the same page. Every one of those is
   a child of .container, so they all share its width and padding, and
   removing the cap here is what lines the text up with them.
   (72ch was the old value - put a max-width back if you ever want a
   narrower, more book-like column of text.) */
.prose { max-width: none; }
.prose p:last-child { margin-bottom: 0; }

/* Wrapper that gives videos and images a framed, rounded appearance. */
.media-frame {
  margin: var(--space-lg) 0;
  padding: 10px;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  background: linear-gradient(160deg, var(--c-surface), var(--c-bg-deep));
  box-shadow: var(--shadow-md);
}
.media-frame__inner {
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--c-bg-deep);
  line-height: 0;   /* Removes the small gap under inline media */
}

/* The <video> element itself.
   aspect-ratio reserves the correct space before the file loads, which
   stops the page "jumping" as it arrives (cumulative layout shift). */
.video {
  width: 100%;
  aspect-ratio: 16 / 9;
  display: block;
  background-color: var(--c-bg-deep);
  object-fit: cover;
}

/* A plain framed image (used on the Kanban Pizza Game page). */
.figure-img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  display: block;
}

/* Caption text under a video or image. */
.media-caption {
  margin: 0.75rem 2px 0;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--c-text-dim);
}

/* Placeholder note boxes, so it is obvious which content is a stand-in. */
.note {
  margin: var(--space-md) 0;
  padding: var(--space-sm) var(--space-md);
  border-left: 3px solid var(--c-accent);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: rgba(56, 189, 248, 0.07);
  font-size: 0.9rem;
  color: var(--c-text-muted);
}
.note p { margin: 0; color: inherit; }
.note strong { color: var(--c-accent-soft); }

/* Tag chips used to list the technologies behind a project. */
.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  list-style: none;
  margin: 0 0 var(--space-lg);
  padding: 0;
}
.tag {
  padding: 0.25rem 0.75rem;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-pill);
  background: var(--c-surface);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--c-text-muted);
}


/* ==========================================================================
   9. FOOTER
   ========================================================================== */

.site-footer {
  margin-top: var(--space-xl);
  padding-block: var(--space-lg);
  border-top: 1px solid var(--c-border);
  background: rgba(3, 7, 26, 0.6);
}

.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}

/* Row of social links (LinkedIn + GitHub). */
.social-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  list-style: none;
  margin: 0;
  padding: 0;
}

.social-link {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 1rem;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-pill);
  background: var(--c-surface);
  color: var(--c-text);
  font-size: 0.92rem;
  font-weight: 500;
  text-decoration: none;
  transition: border-color var(--transition), background var(--transition),
              color var(--transition), transform var(--transition);
}
.social-link:hover,
.social-link:focus-visible {
  transform: translateY(-2px);
  border-color: var(--c-border-hi);
  background: var(--c-surface-hi);
  color: #fff;
}

/* The inline SVG icons inherit the link colour via fill="currentColor". */
.social-link__icon {
  width: 20px;
  height: 20px;
  flex: 0 0 auto;
}
.social-link:hover .social-link__icon { color: var(--c-accent); }

.site-footer__note {
  margin: 0;
  font-size: 0.85rem;
  color: var(--c-text-dim);
}


/* ==========================================================================
   10. RESPONSIVE BREAKPOINTS
   The layout is mobile-friendly by default; these rules adapt it for
   tablets and phones. Breakpoints are in em so they respect the visitor's
   browser font-size setting.
   ========================================================================== */

/* --- Tablets and small laptops (<= 900px) --- */
@media (max-width: 56.25em) {
  /* Award panel becomes a single stacked column. */
  .award { grid-template-columns: 1fr; }
  .award__media { border-right: 0; border-bottom: 1px solid var(--c-border); }
  /* No aspect-ratio here: forcing the image into a 16:9 box would strand a
     near-square picture in a wide, mostly-empty band. Letting it keep its
     own shape, capped a little shorter than on desktop, wastes no space. */
  .award__img { max-height: 200px; }

  .section { padding-block: var(--space-lg); }
}

/* --- Phones and small tablets (<= 720px): switch to the burger menu --- */
@media (max-width: 45em) {
  .nav-toggle { display: inline-flex; }

  /* The menu becomes a full-width dropdown panel under the nav bar.
     It is hidden by default and revealed when main.js sets
     data-open="true" on the <nav> element. */
  .site-nav__list {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0.25rem;
    padding: var(--space-sm);
    background: rgba(6, 13, 36, 0.97);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--c-border);
    box-shadow: var(--shadow-md);

    /* Hidden state: collapsed, faded out, and removed from the tab order by
       visibility:hidden. That last part matters - a menu that is merely
       transparent would still be reachable with the Tab key. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px);

    /* Deliberately NO transition on the closed state.

       This is what stops the menu flickering when the window is resized
       past the breakpoint. Widen-to-narrow means these mobile rules start
       applying to a menu that was, a moment ago, a fully visible row of
       desktop links. If a transition were declared here, the browser would
       dutifully animate it from that visible state down to hidden - so the
       menu would drop open as a panel and fade away, without anyone having
       touched the burger button.

       Declaring the transition only in the open state below means the menu
       animates when it is actually opened, and simply is not there the rest
       of the time. */
    transition: none;
  }

  .site-nav[data-open="true"] .site-nav__list {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);

    /* The opening animation. It applies only while the menu is open, so a
       resize (which never sets data-open) can never trigger it.
       Closing is therefore instant rather than animated - a fair trade for
       a menu that never appears on its own. */
    transition: opacity var(--transition), transform var(--transition);
  }

  .site-nav__link {
    padding: 0.75rem 1rem;
    border-radius: var(--radius-sm);
    font-size: 1rem;
  }

  /* The two project cards stack vertically on narrow screens. */
  .project-grid { grid-template-columns: 1fr; }

  /* Footer content centres itself once it wraps onto two lines. */
  .site-footer__inner { justify-content: center; text-align: center; }
  .social-links { justify-content: center; }
}

/* --- Very small phones (<= 380px) --- */
@media (max-width: 23.75em) {
  .social-link { padding: 0.5rem 0.8rem; font-size: 0.85rem; }
  .site-nav__brand { font-size: 0.95rem; }
}

/* --- Very short windows: landscape phones, or a half-height browser ---
   There is not enough height here for a large banner AND a comfortable
   strip of text, so the reserved text strip shrinks and the banner is
   allowed to go smaller than it would elsewhere. Because the banner height
   in section 1 is built from these two custom properties, overriding them
   is all that is needed - the clamp() recalculates itself.

   Height-based media queries are rare but exactly right here: the problem
   is a shortage of vertical space, which has nothing to do with width. */
@media (max-height: 32.5em) {
  :root {
    --hero-banner-min-h: 110px;
    --hero-text-peek: 52px;
  }
}

/* --- Large desktop monitors ---
   Only the space BELOW the hero grows on a big monitor. The gap above the
   heading stays small so the heading keeps sitting just under the nav bar. */
@media (min-width: 90em) {
  .hero { padding-block: var(--space-md) var(--space-xl); }
}


/* ==========================================================================
   11. USER PREFERENCE MEDIA QUERIES
   ========================================================================== */

/* Respect "reduce motion" OS settings: switch off animation and transitions
   for visitors who find on-screen movement uncomfortable. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .card:hover, .award:hover, .social-link:hover { transform: none; }
  .card:hover .card__img { transform: none; }
}

/* Strengthen borders and text contrast when the OS asks for more contrast. */
@media (prefers-contrast: more) {
  :root {
    --c-border: #4a6db5;
    --c-text-muted: #c6d5f0;
    --c-text-dim: #a7b8d8;
  }
}

/* Sensible printing: white background, black text, no nav or footer chrome. */
@media print {
  body { background: #fff; color: #000; }
  .site-nav, .site-footer, .skip-link, .card__cue { display: none; }
  a { color: #000; text-decoration: underline; }
  .media-frame, .card, .award { border: 1px solid #999; box-shadow: none; }
  p, .card__title, h1, h2, h3 { color: #000; }
}
