/* ======================================================================
   style.css - all styling for the whole site
   ======================================================================
   Every page links to this ONE file, so a change here applies everywhere:

     <link rel="stylesheet" href="style.css">        (pages in the root)
     <link rel="stylesheet" href="../../style.css">  (pages in pages/<name>/)

   The browser caches it after the first page load, so navigating between
   pages does not re-download it.
   ====================================================================== */

/* ===================================================================
   COLOR PALETTE  (CSS custom properties, a.k.a. CSS variables)
   ===================================================================
   ':root' is the <html> element. Variables defined here are inherited by
   every element on the page, so this block is the single source of truth
   for the whole color scheme - change a color once, it changes everywhere.
   Any '--name: value' pair is read back elsewhere with 'var(--name)'. */
:root {
  --bg: #f7f4e3;          /* page background - warm cream                    */
  --card: #fbf9ee;        /* the content card, slightly lighter than bg      */
  --ink: #4a5240;         /* body text - soft dark olive                     */
  --ink-strong: #333a2c;  /* emphasized text, a shade darker                 */
  --line: #c3c6ae;        /* borders and dividers                            */
  --accent: #7f8c5f;      /* olive green - name, links on hover              */
}

/* THE ENTIRE DARK MODE. Same element (<html>), but this rule only applies
   when it carries the attribute data-theme="dark" - which the script at the
   bottom of the page sets. It redefines the same six variables, so every
   rule below that uses var(...) updates automatically.
   This is why the theme toggle only has to change ONE attribute: there is no
   duplicated dark-mode styling anywhere else in this file. */
:root[data-theme="dark"] {
  --bg: #23261f;
  --card: #2b2f26;
  --ink: #c9cfbc;
  --ink-strong: #e6ebd8;
  --line: #4d5344;
  --accent: #a9bb84;
}

/* '*' matches every element. By default CSS 'width' measures ONLY the content,
   so padding and borders get added on top and a "200px" box renders wider.
   border-box makes width mean the total, outer size. Almost every site sets
   this first because the default is so counter-intuitive. */
* { box-sizing: border-box; }

/* ===================================================================
   PAGE BASICS
   =================================================================== */
body {
  margin: 0;                  /* remove the browser's default ~8px margin */
  padding: 3rem 1.5rem 4rem;  /* top | left+right | bottom.
                                 'rem' = multiples of the root font size
                                 (16px by default), so 3rem = 48px */
  background: var(--bg);      /* reads the variables above, so these two   */
  color: var(--ink);          /* lines follow the active theme for free    */
  font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
                              /* fallback chain: each name is tried in order
                                 if the previous font isn't available */
  font-size: 15px;
  line-height: 1.75;          /* unitless = 1.75 x this element's font size.
                                 Unitless is the right habit: it rescales
                                 correctly for children of a different size */
  transition: background 0.25s, color 0.25s;
                              /* animates the theme switch over a quarter
                                 second instead of snapping instantly */
}

/* ===================================================================
   HEADER (name on the left, nav on the right)
   =================================================================== */
header {
  max-width: 1100px;          /* don't stretch across huge monitors */
  margin: 0 auto 4rem;        /* top | left+right | bottom. 'auto' on the
                                 sides splits leftover space evenly, which is
                                 the standard way to center a fixed-width box */
  display: flex;              /* lay the children out in a row */
  align-items: center;        /* ...vertically centered relative to each other */
  justify-content: space-between; /* first child pushed left, last pushed right,
                                     all leftover space dumped between them */
  gap: 1.5rem;                /* minimum spacing between children */
  flex-wrap: wrap;            /* on a narrow screen, let them stack onto a new
                                 line instead of overflowing sideways */
}

/* '.name' is a CLASS selector: it matches any element with class="name". */
.name { font-size: 1.6rem; letter-spacing: 0.12em; color: var(--accent); }
      /* letter-spacing in 'em' is relative to THIS element's font size, so the
         tracking scales proportionally if you change the 1.6rem above */

nav { display: flex; align-items: center; gap: 1.5rem; }
nav a { color: var(--ink); text-decoration: none; }  /* drop the underline */
nav a:hover { color: var(--accent); }   /* ':hover' is a pseudo-class:
                                           state-based styling, no JS needed */

/* '#theme-toggle' is an ID selector: matches the single element with
   id="theme-toggle". IDs must be unique within the page. */
#theme-toggle {
  font: inherit;              /* IMPORTANT: buttons do NOT inherit typography
                                 by default - they use an OS font. This line is
                                 what makes the button use JetBrains Mono */
  font-style: italic;
  color: var(--ink);
  background: none;               /* strip the default grey button fill...    */
  border: 1px solid var(--line);  /* ...leaving an outlined "ghost" button    */
  border-radius: 999px;       /* any radius past half the height is clamped to
                                 a semicircle, so a big number = pill shape */
  padding: 0.25rem 0.9rem;
  cursor: pointer;            /* show the hand cursor; <button> alone doesn't */
}
#theme-toggle:hover { border-color: var(--accent); color: var(--accent); }

/* ===================================================================
   MAIN CARD (photo column on the left, bio text on the right)
   =================================================================== */
main {
  max-width: 1100px;
  margin: 0 auto;             /* centered, same trick as the header */
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;        /* rounded corners */
  padding: 3rem;              /* breathing room INSIDE the card border */
  display: flex;              /* photo column and bio column side by side */
  gap: 3rem;                  /* space between those two columns */
  align-items: flex-start;    /* align both columns to the top. Without this,
                                 flex stretches the photo column to match the
                                 height of the taller bio column */
  flex-wrap: wrap;            /* let the bio drop below the photo when narrow */
}

.avatar {
  width: 200px;
  height: 200px;
  border-radius: 50%;         /* 50% on a square box = a perfect circle */
  object-fit: cover;          /* scale the image to FILL the 200x200 box without
                                 distorting it, cropping whatever overflows.
                                 (The default, 'fill', would squash the photo) */
  object-position: center 20%;/* which part survives the crop: horizontally
                                 centered, 20% down from the top rather than the
                                 default 50% - this keeps the face in frame */
  display: block;             /* images are inline by default and sit on a text
                                 baseline, which leaves a few px of phantom gap
                                 underneath. 'block' removes it */
}

.socials { display: flex; justify-content: center; gap: 2rem; margin-top: 1.75rem; }
.socials a { color: var(--ink); display: inline-flex; }
            /* inline-flex makes the link box hug the icon exactly (again, no
               baseline gap), while still sitting inline next to its siblings */
.socials a:hover { color: var(--accent); }
            /* this recolors the ICONS too - see the currentColor note in the SVGs */

.bio { flex: 1 1 380px; }
      /* shorthand for grow | shrink | basis:
           grow 1   -> take a share of any leftover horizontal space
           shrink 1 -> allowed to get smaller when space is tight
           380px    -> preferred starting width. Below roughly this, the
                       flex-wrap on <main> drops the bio under the photo */
.bio p { margin: 0 0 1.5rem; }           /* space between paragraphs */
.bio p:last-child { margin-bottom: 0; }  /* ...but not after the final one, so
                                            the card's bottom padding stays even */
strong { color: var(--ink-strong); }     /* darker ink for <strong> emphasis.
                                            Currently unused - here for later */

/* ===================================================================
   THE "MAGIC" GRADIENT TEXT
   ===================================================================
   The trick has three cooperating parts, and needs all three:
     1. paint a gradient as the element's background
     2. background-clip: text  -> clip that background to the SHAPE of the
        letters, so the gradient only exists inside the glyphs
     3. color: transparent     -> hide the real text so the gradient shows
   Then animate the background's position to make the colors travel. */
.magic {
  font-weight: 700;           /* bold (the 700 weight is loaded up in <head>) */

  /* The color ramp. Percentages place each stop along the gradient; they are
     deliberately uneven so the purple band is wider and lingers a moment.
     NOTE: the first and last stops are the SAME color (#5f7043) - that is what
     makes the loop seamless, see the @keyframes note below. */
  background-image: linear-gradient(90deg,  /* 90deg = left-to-right ramp */
    #5f7043 0%,               /* olive - matches the site accent  */
    #46796b 18%,              /* moss teal                        */
    #5a5a95 38%,              /* indigo                           */
    #8b5a9e 55%,              /* purple - the peak of the sweep   */
    #6d6a9c 74%,              /* periwinkle, easing back down     */
    #5f7043 100%);            /* olive again - must equal the first stop */

  background-size: 200% 100%; /* make the gradient image twice as wide as the
                                 text box, so only half of it is visible at a
                                 time - that's what leaves room to slide */
  -webkit-background-clip: text;  /* legacy prefix, still needed for Safari */
  background-clip: text;          /* clip the gradient to the letter shapes */
  color: transparent;             /* hide the actual glyph fill so the
                                     clipped gradient is what you see */

  /* The glow. drop-shadow follows the rendered SHAPE of the element, so it
     traces the letters. Plain 'text-shadow' would draw nothing here, because
     the text color is transparent. Two stacked shadows: a tight violet one
     and a wider olive one, so the halo picks up both ends of the gradient.
     Syntax: offset-x | offset-y | blur | color.  In rgba(), the last value is
     alpha (opacity), 0 to 1. */
  filter: drop-shadow(0 0 4px rgba(122, 92, 158, 0.40))
          drop-shadow(0 0 10px rgba(127, 140, 95, 0.30));

  /* shorthand: name | duration | timing function | repeat count.
     'linear' = constant speed; the default 'ease' would visibly speed up and
     slow down on every loop, which reads as a stutter on a continuous sweep. */
  animation: magic-shift 6s linear infinite;
}

/* Dark-mode version: only the gradient colors and the glow are replaced.
   font-weight, background-size, the clipping and the animation all carry over
   from the rule above, since this is the same class. It wins over that base
   rule because the selector is more specific (two parts instead of one),
   regardless of which one comes first in the file. */
:root[data-theme="dark"] .magic {
  background-image: linear-gradient(90deg,
    #a9bb84 0%,               /* the dark-mode accent green */
    #86c6a6 18%,              /* mint       */
    #a79bef 38%,              /* periwinkle */
    #d7a2e6 55%,              /* orchid     */
    #b0b6e0 74%,
    #a9bb84 100%);
  filter: drop-shadow(0 0 5px rgba(183, 160, 235, 0.55))   /* stronger glow:  */
          drop-shadow(0 0 12px rgba(169, 187, 132, 0.35)); /* a dark bg takes it */
}

/* The animation itself. @keyframes defines named waypoints that the
   'animation' property above plays back.
   Why 200% -> 0% and not the other way round: because the gradient image is
   WIDER than its box, percentage positions map onto a negative offset range,
   and the net effect of this direction is the image sliding to the RIGHT by
   exactly one tile width. Because the gradient's first and last stops are the
   same color, the final frame is pixel-identical to the first - so the loop
   restarts invisibly instead of jumping. */
@keyframes magic-shift {
  from { background-position: 200% 50%; }
  to { background-position: 0% 50%; }
}

/* Accessibility: reads an operating-system setting. Visitors who have asked
   their OS to reduce motion (vestibular disorders, migraine triggers, or plain
   preference) get the gradient frozen mid-sweep instead of animating. The text
   stays perfectly readable - only the movement stops. */
@media (prefers-reduced-motion: reduce) {
  .magic { animation: none; }
}

/* ===================================================================
   CONTENT PAGES  (pages/weekN/weekNN.html and friends)
   ===================================================================
   The <main> rule further up is a two-column flex layout built for the
   home page (photo beside bio). Week pages are a single column of prose,
   so they write <main class="page"> to switch that layout off. Everything
   else - the card background, border, padding and centering - is inherited
   from the <main> rule, so the two page types stay visually consistent. */
.page { display: block; }

.page h1 {
  margin: 0 0 0.4rem;         /* no top margin: the card padding handles it */
  font-size: 1.6rem;
  color: var(--ink-strong);
}

.page .subtitle {             /* the small date / one-line summary under the title */
  margin: 0 0 2.5rem;
  color: var(--accent);
}

.page h2 {
  margin: 2.5rem 0 1rem;      /* big gap above, small below: the heading should
                                 sit closer to the text it introduces than to
                                 the section it follows */
  font-size: 1.1rem;
  color: var(--ink-strong);
  border-bottom: 1px solid var(--line);
  padding-bottom: 0.4rem;
}

.page p { margin: 0 0 1.5rem; }
.page p:last-child { margin-bottom: 0; }

/* Images scale down on narrow screens instead of overflowing the card.
   'max-width: 100%' is the single most useful line for responsive images. */
.page img {
  max-width: 100%;      /* scale down on narrow screens instead of overflowing */
  height: auto;         /* ...keeping the proportions while it does */
  border-radius: 8px;
  display: block;
  margin-left: auto;    /* centre an image narrower than the card, to match the */
  margin-right: auto;   /* centred captions underneath                          */
}

/* <figure> groups an image with its caption; <figcaption> is the caption. */
.page figure { margin: 0 0 2rem; }
.page figcaption {
  margin-top: 0.6rem;
  font-size: 0.9em;
  font-style: italic;
  text-align: center;   /* centred under the figure it describes. 'text-align'
                           is inherited, so this also centres a caption that
                           wraps onto two lines */
}

/* EMBEDDED VIEWERS (the Fusion share, video embeds).
   Note this styles the FRAME only. What is inside a cross-origin iframe cannot
   be reached by your CSS - the browser's same-origin policy blocks it - so the
   Autodesk viewer's own background and toolbar stay as Autodesk ships them.

   An <iframe> is INLINE by default, so it sits on a text line and hugs the left
   edge. 'display: block' takes it out of the text flow; the auto side margins
   then centre it, which matters again if you ever give it a max-width. */
.page iframe {
  display: block;
  margin-left: auto;
  margin-right: auto;
  width: 100%;          /* fill the card's column instead of the fixed 800px in
                           the width="" attribute. Also stops the embed
                           overflowing the card on a phone */
  aspect-ratio: var(--ratio, 16 / 9);
      /* The shape of the box, at any width. 16:9 is the default because that is
         what video is: give a YouTube embed any other ratio and YouTube keeps
         the film its true shape and pads the rest with BLACK BARS - it will not
         crop or stretch your video to fit.
         The Fusion viewer has no fixed shape of its own, so it simply fills
         whatever box it is given and 16:9 suits it fine.
         For an embed that does need a different shape, set the variable on that
         one element and this rule picks it up:
             <iframe style="--ratio: 4 / 3" ...>                              */
  height: auto;         /* beats the height="600" attribute so aspect-ratio wins */
}

/* Code blocks: <pre> preserves whitespace and line breaks exactly as typed
   (unlike every other HTML element, which collapses runs of whitespace). */
.page pre {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 1rem;
  overflow-x: auto;           /* long lines scroll sideways inside the box
                                 rather than stretching the whole page */
  line-height: 1.5;
}
.page code { font-family: inherit; }  /* the page font is already monospace */

/* Lists inside a content page. The browser's default indent is deeper than the
   text needs here, so pull it in to line up better with the paragraphs. */
.page ul,
.page ol {
  margin: 0 0 1.5rem;
  padding-left: 1.4rem;   /* the space the bullets/numbers sit in */
}
.page li { margin-bottom: 0.8rem; }   /* room for the notes below to breathe */

/* A NOTE attached to the point above it - an aside expanding on a bullet,
   rather than a new claim of its own. Written as a <p class="note"> INSIDE the
   <li> it belongs to (an <li> can hold full content: paragraphs, images, even
   another list, not just one line of text).

   Three cues separate it from the bullet, none of them loud:
     - the left rule, which visually ties it to the item above
     - slightly smaller text
     - slightly reduced contrast
   Using a class rather than a bare '.page li p' keeps it deliberate: a note is
   marked as one, and it works outside a list too. */
.page .note {
  margin: 0.5rem 0 0;       /* hugs the bullet above it, no gap below */
  padding-left: 0.9rem;     /* the gap between the rule and the text  */
  border-left: 2px solid var(--line);
  font-size: 0.92em;        /* 'em' = relative to the text around it, so this
                               stays proportional wherever a note is used */
  opacity: 0.85;
}

/* The rule is barely visible against a dark card at the default color, so give
   it the accent instead - dark mode needs a little more contrast to read. */
:root[data-theme="dark"] .page .note { border-left-color: var(--accent); }

/* Links inside body text SHOULD look like links - unlike the tiles, where the
   whole box is obviously clickable. Underline on hover only keeps the text
   readable while still marking it. */
.page a { color: var(--accent); text-decoration: none; }
.page a:hover { text-decoration: underline; }

/* DOWNLOAD BOXES. A row of small bordered boxes, one per file; the whole box
   is the link. 'flex-wrap' drops boxes onto a new line when the row is full,
   so the same markup works on a phone. */
.files {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 1rem 0;
}

.page a.file {
  display: flex;
  flex-direction: column;   /* name on top, description underneath */
  padding: 0.5rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 0.85em;
}

.page a.file:hover {
  border-color: var(--accent);  /* the border lights up instead of an underline */
  text-decoration: none;
}

.file-name { font-weight: 700; }
.file-desc { color: var(--ink); opacity: 0.8; }

/* SPACING UTILITIES.
   Read this before reaching for one: almost everything on the page already
   spaces itself. A <p>, <h2>, <figure> or <ul> carries its own margin, so if
   something looks jammed against its neighbour, the usual cause is loose text
   that was never wrapped in a real element - a bare line sitting straight
   inside <main> gets no margin because it is not an element at all. Wrapping
   it in <p> is the fix, not a spacer.

   When you genuinely want extra room at one particular spot, put this class on
   the element BELOW the gap:
       <h2 class="space-top">Next section</h2>
   For a one-off, an inline style says it just as clearly:
       <h2 style="margin-top: 4rem">Next section</h2>

   Do NOT stack <br><br> to push things apart. <br> means "a line break inside
   a run of text" - a postal address, a line of a poem. Using it for layout
   fakes space with content, and how much you get depends on the font size
   rather than on any number you chose.

   Two classes beat '.page p' on specificity, so no !important is needed. */
.page .space-top    { margin-top: 3rem; }
.page .space-top-sm { margin-top: 1.5rem; }
.page .space-top-lg { margin-top: 5rem; }

/* ===================================================================
   PROJECT TILES  (the grid of weekly projects on the home page)
   ===================================================================
   CSS Grid, not flexbox. The difference is worth knowing:
     - flexbox lays out items along ONE axis and lets them size themselves,
       which is why it suits the header and the two-column card above
     - grid defines the columns UP FRONT and drops items into them, so every
       tile in a row is exactly the same width whatever it contains
   For a tidy 3-across gallery, grid is the right tool. */
.projects {
  max-width: 1100px;      /* same width as the header and the card, so all */
  margin: 4rem auto 0;    /* three edges line up down the page             */
}

.projects h2 {
  margin: 0 0 1.5rem;
  font-size: 0.85rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;   /* small caps-ish label, not a loud heading */
  color: var(--accent);
}

.tiles {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
      /* THE LINE THAT SETS THREE PER ROW.
         '1fr' is a grid-only unit meaning "one share of the free space", so
         repeat(3, 1fr) is three equal columns that absorb the leftover width.
         Items wrap onto a new row automatically once three are placed - you
         never declare how many rows there are, so adding a 16th tile later
         just works. */
  gap: 1.5rem;            /* space between tiles, both directions */
}

/* Three narrow columns become unreadable on small screens, so step down to
   two, then one. These are 'breakpoints': the widths where the layout changes.
   Note max-width media queries are evaluated in order, so the 600px rule must
   come AFTER the 900px one to win on a phone. */
@media (max-width: 900px) { .tiles { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .tiles { grid-template-columns: 1fr; } }

/* A single tile. Written as <a class="tile"> when the page exists, and as
   <div class="tile upcoming"> when it does not yet - see the .upcoming rule. */
.tile {
  display: flex;
  flex-direction: column;   /* stack label, title and description vertically */
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 1.4rem;
  min-height: 140px;        /* keeps short tiles from looking squashed. Grid
                               already stretches every tile in a row to match
                               the tallest one, so this is just a floor */
  text-decoration: none;    /* the <a> tiles should not look like links... */
  color: inherit;           /* ...and should not turn link-blue either */
  transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s;
}

/* Hover only on 'a.tile' - the element selector means unfinished .upcoming
   tiles (which are <div>s, not links) stay inert, so nothing invites a click
   that would go nowhere. */
a.tile:hover {
  border-color: var(--accent);
  transform: translateY(-3px);            /* lifts the tile toward the reader */
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.08);
}
/* A near-black shadow is invisible against a dark background, so dark mode
   needs a deeper one to read as a shadow at all. */
:root[data-theme="dark"] a.tile:hover { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35); }

.tile-num {                 /* the small "WEEK 01" label */
  font-size: 0.75rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}
.tile-title {
  margin: 0.5rem 0 0.4rem;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink-strong);
}
.tile-desc {
  margin: 0;
  font-size: 0.85rem;
  opacity: 0.8;             /* slightly recedes behind the title */
}

/* Optional thumbnail. Uncomment the <img> in a tile to use it.
   The fixed height plus object-fit means photos of any shape crop to the same
   band, so a row of tiles stays even - the same trick as the avatar. */
.tile-thumb {
  width: 100%;
  height: 120px;
  object-fit: cover;
  border-radius: 8px;
  margin-bottom: 0.9rem;
  display: block;
}

/* Weeks not written yet: dashed outline and faded, so the page shows the whole
   arc of the term while making clear what is done and what is not.
   To activate one, change its <div> to <a href="pages/weekN/weekNN.html"> and drop
   the 'upcoming' class. */
.tile.upcoming {
  border-style: dashed;
  opacity: 0.5;
}
.tile.upcoming .tile-title { font-weight: 400; }

/* The final project gets the full width of the grid.
   '1 / -1' means "from the first column line to the last" - -1 counts back
   from the end, so this keeps working if you ever change the column count. */
.tile-final { grid-column: 1 / -1; }

/* Same courtesy as the gradient text: no motion for visitors who asked their
   OS to reduce it. The color change on hover stays, so the tile still
   responds - it just does not move. */
@media (prefers-reduced-motion: reduce) {
  a.tile:hover { transform: none; }
}

