/* ============================================================
   blog3.css - the blog / article style set with Prism.js syntax highlighting
   ------------------------------------------------------------
   Added Prism.js for better Perl syntax highlighting support.
   ============================================================ */

/* ============================================================
   blog.css - the blog / article style set
   ------------------------------------------------------------
   Extracted from blog.html (the "HTML & CSS - A Gentle
   Introduction" page). This is the lighter, article-oriented
   style set: a fixed 900px reading column, monospace type, navy
   accents, dotted rules, code blocks and tag bubbles.

   It is a DIFFERENT style set from template.css. template.css
   is the big rem-scaled one used by the resume and the Web
   Admin page. This one is simpler and fixed-size. Do not mix
   the two on the same page.

   HOW TO USE:
     Either paste this whole file into a <style> block inside the
     <head> of your HTML file, or save it as a .css file and link
     it:  <link rel="stylesheet" href="assets/css/style.css">
     The @import line MUST stay first, before any other rule, or
     the font will not load (this is only a concern if you paste
     it inline; a linked file is fine).

   STRUCTURE:
     PART 1 - Base. Variables, type, links, code, bubbles.
              This is the original extraction, unchanged.
     PART 2 - Optional extensions. Extra elements a blog post
              often needs (h3, lists, quotes, notes, figures,
              tables). These were NOT on the original page. They
              are added here in the same style so you have a
              complete vocabulary. Delete this part if you want
              a byte-for-byte copy of the original.
     PART 3 - Prism.js syntax highlighting. Token colours plus
              a guard that stops the page's own classes (notably
              `.tag`) from bleeding into code blocks.
     PART 4 - House style. The preferred site-wide look: centred
              titles, balanced dotted rules, roomier subtitles and
              larger tag bubbles. Delete this part to fall back to
              the PART 1/2 defaults.

   THE COLORS (change them in one place, in :root):
     --navy    #1E3765   the accent. Headings, links, bubbles.
     --ink     #333      main body text.
     --muted   #555      secondary text, captions, footer.
     --border  #ccc      dotted box and rule borders.
   ============================================================ */

/* ============================================================
   PART 1 - BASE (from the original page)
   ============================================================ */

@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible+Mono:wght@400;700&display=swap');

:root {
  --navy: #1E3765;
  --ink: #333;
  --muted: #555;
  --border: #ccc;
}

html { font-size: 16px; }

body {
  font-family: 'Atkinson Hyperlegible Mono', monospace;
  background-color: white;
  color: var(--ink);
  margin: 0;
  padding: 2rem;
  line-height: 1.75;
}

main { max-width: 900px; margin: 0 auto; overflow-x: hidden; }
img { max-width: 100%; height: auto; }
.slide-frame { max-width: 100%; margin: 1.2rem 0; }
.slide-frame img { display: block; margin: 0 auto; max-width: 100%; height: auto; }

h1 { font-size: 2.2rem; color: var(--navy); margin-bottom: 0.2rem; }
h2 {
  font-size: 1.4rem;
  color: var(--navy);
  margin: 3rem 0 1rem;
  border-bottom: dotted 2px var(--border);
  padding-bottom: 0.4rem;
}
/* Long-form reading: 0.6rem left paragraphs hugging. A bit over
   1rem of vertical margin (collapses between neighbours) opens air
   without looking like double-spaced homework. */
p  { margin: 1.15rem 0; }
a  { color: var(--navy); text-decoration: none; border-bottom: dotted 1px var(--navy); }
a:visited { color: var(--navy); }
a:hover { background-color: var(--navy); color: white; }
a:visited:hover { background-color: var(--navy); color: white; }
/* Code chips inside links: inherit link color so visited stays navy
   on the gray pill, and hover inverts the chip with the link. */
a code { color: inherit; }
a:hover code,
a:visited:hover code {
  background-color: transparent;
  border-color: rgba(255, 255, 255, 0.55);
  color: white;
}

code {
  background: #f2f2f2;
  border: dotted 1px #999;
  border-radius: 0.3rem;
  padding: 0.1rem 0.4rem;
  font-size: 0.9rem;
}

pre {
  background: #f7f7f7;
  border: dotted 2px var(--border);
  border-radius: 1rem;
  padding: 1.2rem 1.4rem;
  overflow-x: auto;
  font-size: 0.95rem;
  margin: 1.4rem 0;
}
pre code { background: none; border: none; padding: 0; }

.intro { border: dotted 2px var(--border); border-radius: 2rem; padding: 1.6rem 2rem; }
.intro b { color: var(--navy); }

.tag { display: inline-block; border: dotted 1px #777; border-radius: 0.4rem; padding: 0.15rem 0.6rem; font-size: 0.85rem; color: var(--muted); margin: 0.2rem; }
.tag:hover { background-color: var(--navy); border-color: var(--navy); color: white; }

footer { margin-top: 4rem; font-size: 0.85rem; color: var(--muted); text-align: center; }

/* ============================================================
   PART 2 - OPTIONAL EXTENSIONS (added, not in the original)
   ------------------------------------------------------------
   A blog post usually needs a few more elements. These are
   written in the same style, using the same variables. Keep
   them, or delete the whole of PART 2 for the original look.
   ============================================================ */

/* The line under the title: a one-line summary of the post. */
.subtitle { font-size: 1.05rem; color: var(--muted); margin: 0 0 0.4rem; }

/* The small date / author line under the title. */
.meta { font-size: 0.85rem; color: var(--muted); margin: 0 0 2rem; }

/* The post date alone, centred. The reader wanted the date that
   heads a piece to sit in the middle of the column, the way the
   deck cards already centre their `.meta` dates. This is its own
   class rather than a change to `.meta`, because `.meta` is also
   the left-aligned subtitle on the resume entries and the front
   page project entries, and those must stay flush under their
   headings (see PART 3's note on that).
   Only `text-align` changes; size, colour and margin are `.meta`'s. */
.meta--date { text-align: center; }

/* Third-level heading, for subsections inside an h2. */
h3 { font-size: 1.15rem; color: var(--navy); margin: 2rem 0 0.6rem; }

/* Fourth-level heading. Used for the line under a page title,
   e.g. the institution beneath the job title. Muted and small,
   so it reads as a sub-line rather than another heading. */
h4 { font-size: 0.95rem; color: var(--muted); font-weight: 400; margin: 0 0 1rem; }

/* Lists. */
ul, ol { margin: 0.8rem 0 1rem 1.6rem; padding: 0; }
li { margin-bottom: 0.4rem; }
li::marker { color: var(--navy); }

/* A dotted divider between sections. */
hr { border: none; border-top: dotted 2px var(--border); margin: 3rem 0; }

/* A pull quote. */
blockquote {
  margin: 1.6rem 0;
  padding: 0.6rem 0 0.6rem 1.4rem;
  border-left: dotted 4px var(--navy);
  color: var(--muted);
  font-style: italic;
}

/* A note / callout box, to make one point stand out. */
.note {
  border: dotted 2px var(--navy);
  border-radius: 1rem;
  padding: 1rem 1.4rem;
  margin: 1.6rem 0;
  font-size: 0.95rem;
}
.note b { color: var(--navy); }

/* A wrapper for a row of tag bubbles. */
.tag-row { margin: 1rem 0; line-height: 2.4; }

/* A centred image with a caption. */
figure { margin: 2rem 0; text-align: center; }
figure img { max-width: 100%; border-radius: 1rem; border: dotted 2px var(--border); }
figcaption { margin-top: 0.6rem; font-size: 0.85rem; color: var(--muted); }

/* A simple data table. */
table { border-collapse: collapse; margin: 1.6rem 0; font-size: 0.95rem; }
th, td { border: dotted 1px #999; padding: 0.5rem 0.9rem; text-align: left; }
th { color: var(--navy); }

/* A list of links, each with a one-line description of where it
   goes. Used on links/index.html in place of a table.

   WHY IT IS NOT A TABLE: a table puts a dotted box around every
   cell (see `th, td` above), so each link ends up inside its own
   rectangle. On a page whose whole content is links, that reads as
   a border on the link. Here the grouping is carried by other
   means instead, and nothing boxes the link in:
     - the label is small, uppercase and navy, so it reads as a
       marker for the entry rather than a cell in a grid;
     - entries are separated by a dotted rule BETWEEN them only
       (`border-bottom` on the description), which is the same
       divider language as `hr` and the `h2` underline;
     - the link keeps the site's ordinary dotted-underline treatment
       from `a` in PART 1 · it is not restyled, so it still reads as
       a link and not as a button.
   The rule is on `dd`, not `dt`, so it sits under the description
   where the entry actually ends.

   <dl class="link-list">
     <dt><a href="…">Luma</a></dt>
     <dd><span class="link-note">what it is</span></dd>
   </dl>

   The title is the click, like a paper title. The hostname stays
   in href only. The note is quiet. */
.link-list { margin: 1.6rem 0; }
.link-list dt {
  color: var(--navy);
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: 0.15rem;
}
.link-list dt a {
  color: inherit;
}
.link-list dd {
  margin: 0 0 1rem;
  padding-bottom: 1rem;
  border-bottom: dotted 1px var(--border);
  color: var(--muted);
}
/* The last entry has nothing below it to be separated from, so the
   rule there would just hang in space above the next `h2`. */
.link-list dd:last-child {
  margin-bottom: 0;
  padding-bottom: 0;
  border-bottom: none;
}
/* The muted half of an entry: the description after the link. */
.link-note { color: var(--muted); }

/* ============================================================
   PART 3 - PRISM.JS SYNTAX HIGHLIGHTING
   ------------------------------------------------------------
   Prism.js provides better Perl syntax highlighting than
   Highlight.js. Add this to your HTML <head>:

   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css">
   <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>

   The autoloader pulls in whichever grammar a block asks for,
   so Perl, Bash, HTML, CSS, JS, Python, etc. all "just work"
   with no extra <script> tags. Nothing here is tied to a path,
   so this file can be copied anywhere.

   Then use: <pre><code class="language-perl">...</code></pre>
   ============================================================ */

/* Prism.js theme overrides for navy styling */
code[class*="language-"],
pre[class*="language-"] {
  color: var(--ink);
  text-shadow: none;
  font-family: 'Atkinson Hyperlegible Mono', monospace;
}

pre[class*="language-"] {
  background: #f7f7f7 !important;
  border: dotted 2px var(--border);
  border-radius: 1rem;
}

/* Keywords - navy blue */
.token.keyword { color: #0000FF; font-weight: bold; }

/* Variables - green */
.token.variable { color: #008000; }

/* Strings - red */
.token.string { color: #A31515; }

/* Comments - gray, italic */
.token.comment { color: #808080; font-style: italic; }

/* Numbers - teal */
.token.number { color: #098658; }

/* Operators - darker gray to match background */
.token.operator { color: #555 !important; }

/* Functions - green */
.token.function { color: #79B642; }

/* Regex - red */
.token.regex { color: #FF0000; }

/* Punctuation */
.token.punctuation { color: #808080; }

/* Custom Perl token colors */
.token.regex-variable { color: #008000; }

/* Special variables like $&, @arr, %hash */
.token.regex-variable { color: #008000; }

pre[class*="language-"] code {
  background: transparent !important;
}

/* Ensure operator color matches muted text */
.token.operator,
.token.punctuation {
  color: #808080 !important;
}

/* ------------------------------------------------------------
   Extended token vocabulary (Perl, Bash, HTML, CSS, and more)
   ------------------------------------------------------------ */

/* Booleans / constants - blue */
.token.boolean,
.token.constant,
.token.symbol { color: #0000FF; }

/* Class names / built-ins - teal */
.token.class-name,
.token.builtin { color: #267F99; }

/* HTML/XML tags and CSS selectors - navy accent.
   IMPORTANT: the blog stylesheet already uses `.tag` for the little
   dotted "tag bubbles". Prism emits <span class="token tag"> for every
   HTML element, which would inherit that bubble border/padding and
   wreck every code block. These resets keep tokens plain text. */
.token.tag,
.token.selector { color: var(--navy); }

.token.tag {
  display: inline;
  border: none;
  border-radius: 0;
  padding: 0;
  margin: 0;
  background: transparent;
  font-size: inherit;
}

/* General guard: no token should ever pick up box styling from the
   page's own component classes. */
.token {
  border: none;
  border-radius: 0;
  padding: 0;
  margin: 0;
  background: transparent !important;
}

/* Attribute names - teal; values - red */
.token.attr-name { color: #267F99; }
.token.attr-value,
.token.char { color: #A31515; }

/* Perl sigils, package names, labels */
.token.package,
.token.important { color: #0000FF; font-weight: bold; }
.token.label,
.token.shebang { color: #808080; font-style: italic; }

/* Bash / shell builtins and variables */
.token.function-variable { color: #008000; }
.token.environment { color: #008000; }

/* Deleted / inserted (diff-style blocks) */
.token.deleted { color: #A31515; }
.token.inserted { color: #008000; }

/* ============================================================
   PART 4 - HOUSE STYLE (the preferred site-wide look)
   ------------------------------------------------------------
   These are the tweaks that were dialled in on the demo page.
   They are promoted here so EVERY page that links this sheet
   gets the same look, instead of each page re-declaring them
   in an inline <style> block.

   Nothing here is page-specific: no paths, no fixed widths, no
   image rules. Copy this file anywhere and it behaves the same.

   Delete this whole PART 4 to fall back to the PART 1/2 look.
   ============================================================ */

/* A tighter top margin than the PART 1 default of 2rem, so a
   hero image or title does not sit far down the page. */
body {
  padding-top: 0.75rem;
}

/* Centred page title. PART 1 left it flush-left with a 0.2rem
   gap; the house look centres it and gives the rule below some
   breathing room. */
h1 {
  text-align: center;
  margin-bottom: 1.15rem;
  /* Body line-height (1.75 / 1.8 on phones) is for paragraphs. On a
     narrow screen the name can wrap to two lines; that body leading
     left a huge gap between "Michael" and "Limberger". Keep the title
     tight. */
  line-height: 1.15;
}

/* Section headings. PART 1 is 3rem above and 1rem below, which
   still parks an h2 tight against the block above it (a table,
   an intro, the previous section). Extra top margin drops the
   heading so sections breathe. A little more below the dotted
   underline so the first paragraph is not sitting on the rule. */
h2 {
  margin: 4.5rem 0 1.4rem;
  padding-bottom: 0.55rem;
}

/* The desk (front page, resume) is an h2 then a table. PART 1
   gives the table 1.6rem top margin, which collapses with the
   h2's 1.4rem bottom into a single 1.6rem, so the grid sits
   against the dotted underline. A larger top margin on that
   pairing opens the gap without moving every other table. */
h2 + table {
  margin-top: 2.8rem;
}

/* Dotted rules: no gap above, a consistent 1rem below. The
   PART 2 default was 3rem top AND bottom, which pushed content
   away from the rule it was meant to sit under. */
hr {
  margin-top: 0;
  margin-bottom: 1rem;
}

/* The subtitle under the title. Needs clearance from the rule
   above and from whatever follows (usually the .intro box). */
.subtitle {
  margin-top: 1.4rem;
  margin-bottom: 1.6rem;
}

/* The date / author line (.meta) is intentionally LEFT-aligned:
   in a dated entry (a job, a qualification) it must line up under
   the heading above it. Do not centre it here. Where a .meta line
   sits inside a footer, PART 1's `footer { text-align: center }`
   takes care of centring it. */

/* Tag rows: centred, evenly spaced between the rules that
   usually bracket them, and with a saner line-height than the
   PART 2 default of 2.4 (which added phantom space below). */
.tag-row {
  text-align: center;
  margin: 1rem 0;
  line-height: 1.8;
}

/* Left-aligned tag row, for a strip of tech badges under a
   project entry. The centred default is for a nav row between
   two rules; under a heading, flush left reads better.
   The negative left margin cancels the bubbles' own 0.35rem
   left margin, so the first bubble lines up with the text. */
.tag-row--left {
  text-align: left;
  margin: 0.6rem 0 1.6rem -0.35rem;
}

/* Tag bubbles: larger and chunkier than the PART 1 default. */
.tag-row .tag {
  font-size: 1.05rem;
  padding: 0.35rem 1rem;
  margin: 0.35rem;
}

/* The main menu. A row of tag bubbles used as links, sitting directly
   under the h1. Bigger and bolder than an ordinary tag row, and the
   underline PART 1 puts on links is removed so they read as buttons.

   WHY THERE IS NO `hr` ABOVE OR BELOW: the menu used to be bracketed
   by two rules, but the bubbles are already a 4 + 4 grid of dotted
   boxes, and a rule plus a box plus a rule was three horizontal lines
   stacked inside a few rem. The grid carries the separation on its
   own, so the rules are gone from the markup. The `hr` under the h1
   above it stays: that one separates the name from the menu.

   WHY A GRID AND NOT A WRAPPING LINE: there are eight items. Left
   to `text-align: center` they wrapped wherever the line happened
   to run out, which gave two rows of unequal length and made the
   block look lopsided. Four fixed columns give a deliberate 4 + 4
   instead, and it stays 4 + 4 no matter how long a label is or
   how many items there are.
   The columns are equal (1fr) rather than content-sized, which is
   what makes the two rows line up vertically instead of stepping
   in and out by label width. */
.tag-row--menu {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.8rem;
  text-align: center;
}
/* The grid gap now does the spacing, so the bubbles drop the
   margins they needed back when they were inline-blocks in a
   text line. The padding stays: it is what makes the pill. Grid
   items stretch to fill their column by default, so all eight
   come out the same width. */
.tag-row--menu .tag {
  font-size: 1.25rem;
  padding: 0.5rem 1.5rem;
  margin: 0;
  border-width: 2px;
  box-sizing: border-box;
  min-width: 0;
  width: 100%;
  max-width: 100%;
}
/* On a phone, four columns squeeze the pills until the longer
   labels overflow or get clipped by main's overflow-x: hidden.
   Two columns keep the same even look. Shrink the pill padding
   so "GitHub" and friends still fit inside the column. */
@media (max-width: 700px) {
  /* Mobile reading: keep the same 16px base as desktop (perl.gg
     body is ~17px via clamp; shrinking to 15/14.5 made us smaller
     than perl.gg). Comfort comes from line-height and paragraph
     gaps, not a smaller root size. */
  html {
    font-size: 16px;
  }
  body {
    line-height: 1.8;
    padding: 1.35rem;
  }
  /* Keep the site name tighter on phones; body leading is for prose. */
  h1 {
    font-size: 1.85rem;
    line-height: 1.12;
    margin-bottom: 0.85rem;
  }
  .tag-row--menu {
    margin-top: 0.55rem;
    margin-bottom: 0.55rem;
  }
  .page-title {
    margin-top: 0.25rem;
  }
  .eyebrow {
    font-size: 1.25rem;
    letter-spacing: 0.08em;
    margin-top: 0.25rem;
  }
  .page-title h4 {
    margin-bottom: 1.25rem;
  }
  p {
    margin: 1.25rem 0;
  }
  .intro {
    padding: 1.25rem 1.35rem;
  }
  .intro p {
    margin: 1.3rem 0;
  }
  .intro p:first-child {
    margin-top: 0;
  }
  .intro p:last-child {
    margin-bottom: 0;
  }
  .entry p {
    margin: 1.25rem 0;
  }
  .tag-row--menu {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.55rem;
    width: 100%;
    max-width: 100%;
  }
  .tag-row--menu .tag {
    font-size: 1.05rem;
    padding: 0.45rem 0.55rem;
  }
}
@media (max-width: 400px) {
  /* Still 16px root. A touch more line and padding only. */
  body {
    padding: 1.15rem;
    line-height: 1.85;
  }
  h1 {
    font-size: 1.7rem;
    line-height: 1.1;
  }
  p {
    margin: 1.35rem 0;
  }
  .tag-row--menu .tag {
    font-size: 0.98rem;
    padding: 0.4rem 0.4rem;
  }
}
a.tag {
  border-bottom: dotted 1px #777;
}
a.tag:hover {
  background-color: var(--navy);
  border-color: var(--navy);
  color: white;
}

/* Hover fade. PART 1 snaps `a:hover` and `.tag:hover` to a navy
   fill and white type with no transition, so the invert pops.
   Background, colour, and border ease in so menu pills and
   in-prose links fade into that navy. Short on purpose: it
   should still read as a click, not a dissolve.
   `.deck > a` keeps its own transition (lift, not invert). */
a,
.tag {
  transition: background-color 0.22s ease, color 0.22s ease, border-color 0.22s ease;
}
@media (prefers-reduced-motion: reduce) {
  a:not(.to-top),
  .tag {
    transition: none;
  }
}

/* Menu items that are NOT links: "Links" points at the page's own
   Elsewhere section, which sits right below the menu, so jumping to
   it did nothing. It is a plain <span class="tag"> now. This keeps
   the bubble looking identical, keeps the hover inversion so the row
   still feels alive, but drops the pointer cursor so it does not
   pretend to be clickable. Everything else in the menu is a real
   destination and stays an <a>. */
.tag-row--menu span.tag {
  cursor: default;
}


/* Contact under the site name, above the menu. Centred and muted. */
.site-email {
  text-align: center;
  margin: 0.15rem 0 1.75rem;
  font-size: 0.95rem;
  color: var(--muted);
}
.site-email a {
  color: var(--muted);
  border-bottom: dotted 1px var(--border);
}
.site-email a:hover {
  background-color: var(--navy);
  color: white;
  border-bottom-color: var(--navy);
}
@media (max-width: 700px) {
  .site-email {
    margin-top: 0.1rem;
    margin-bottom: 1.4rem;
  }
}

/* Hero / portrait block. A figure at the top of a page, sitting
   tight against the heading below it, with no dotted frame (the
   PART 2 default frames every image, which looks heavy on a
   full-width portrait). */
.hero { margin-bottom: 0.15rem; }
.hero figure { margin-top: 0; margin-bottom: 0; }
.hero figure img { border: none; }
.hero figcaption { margin-top: 0.2rem; }

/* Portrait: sways gently all the time, and grows on hover.
   Two separate things are happening, and they have to be kept
   apart or they fight over the same property:
     - the GROW is `scale`, with a transition, so it eases in on
       hover and eases back out when the pointer leaves;
     - the SWAY is `rotate`, an infinite animation, running at all
       times so the logo is never quite still.
   If both were done with `transform`, the animation would win
   outright and the grow would snap in with no transition (and
   snap out again the moment the sway animation is removed).
   The individual `scale` / `rotate` properties compose instead
   of overriding each other, which is what makes the two effects
   sit on one element cleanly.
   `transform-origin` is at the bottom so the rock reads as a
   pendulum rather than a spin around the middle. The image is
   `display: block` + auto margins so it stays centred while
   being transformed. */
.hero figure img {
  display: block;
  margin: 0 auto;
  transform-origin: 50% 100%;
  transition: scale 0.35s ease;
  animation: hero-sway 1.5s ease-in-out infinite;
}
.hero figure img:hover {
  scale: 1.04;
}
@keyframes hero-sway {
  0%, 100% { rotate: -1.6deg; }
  50%      { rotate: 1.6deg; }
}

/* Anyone who has asked their system to reduce motion still gets
   the grow, but no movement at rest and no endless rocking. */
@media (prefers-reduced-motion: reduce) {
  .hero figure img {
    transition: none;
    animation: none;
  }
}

/* An "eyebrow" label: the small word that sits above the role line
   in the page-title block (e.g. "Resume" over "Senior Web & Linux
   Administrator"). It uses a <p> rather than a heading on purpose · the h3 below it is the real heading for that block, and a second
   heading here would split the document outline. Sized between the
   role line (h3, 1.15rem) and the page's h1 (2.2rem) so it reads as
   a label above the role, not a title competing with the name.
   Uppercase + letter-spacing so it reads as a label rather than the
   start of a sentence. PART 1's `p` margin is overridden
   because the gap is tuned against the surrounding rules. */
.eyebrow {
  text-align: center;
  font-size: 1.6rem;
  color: var(--navy);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  margin: 0.6rem 0 0.1rem;
}

/* The eyebrow sits directly on top of the role line, but PART 1 gives
   h3 a 2rem top margin, which would open a gap between the two. Pull
   it in when an eyebrow precedes it. */
.eyebrow + h3 {
  margin-top: 0.2rem;
}

/* The page's own title block: the role line and the institution
   line beneath it, both centred. */
.page-title {
  text-align: center;
}
.page-title h3 {
  margin-bottom: 0.3rem;
}
.page-title h4 {
  margin-bottom: 2rem;
}

/* Tables stretch to the full column width, so they line up with
   the headings and paragraphs above and below. PART 1 left them
   auto-width, which made a two-column table hug its content. */
table {
  width: 100%;
}

/* More air inside a cell. PART 1's 0.5rem 0.9rem was tuned for a
   table that hugged its content; now that tables span the full
   column, the text sat hard against the dotted grid and the rows
   read as one block. This is padding only, so nothing moves except
   the space around the words.

   Raised again from 0.8rem 1.2rem: once the grid went to the site's
   heavier 2px dots, the rows read as tighter than they are, so the
   cells get a little more room on all four sides to keep the words
   off the dots. Padding only; no width or colour change. */
th,
td {
  /* Equal, modest padding on every cell. Was 1.1rem / 1.4rem and
     crowded wide score tables. */
  padding: 0.45rem 0.65rem;
}

/* A touch more air above the first row only, so the table's top
   edge does not crowd the heading it sits under. This is the top
   padding alone; the other three sides keep the shared cell padding
   above, and the grid weight and corner radii are untouched. */
/* First row uses the same padding as every other row. */

/* Rounded table edges. Every other framed thing on the site has a
   radius (.intro, pre, figure img, .tag, the cards), so a table
   with square corners read as the odd one out.

   WHY IT IS ON THE CELLS, NOT ON `table`: with collapsed borders the
   cell edges are merged into one shared line and a radius on a cell
   is dropped (a radius on `table` is dropped too, since the table
   owns no border of its own). So the outer corners are rounded on
   the four cells that own them, and `border-collapse` is switched
   to `separate` with `border-spacing: 0`. The spacing keeps the
   cells touching, so the dotted grid looks exactly as it did.

   WHY ONLY THE FOUR CORNER CELLS: rounding every cell in the first
   and last column would scallop the left and right border at every
   row, which reads as five separate boxes instead of one table.
   Only the four cells at the outer corners carry a radius; their
   other three corners stay square, so the dotted edge runs flush
   into the next cell.

   WHY THE BORDER IS DRAWN ON TWO SIDES ONLY: in the separate model
   every cell draws its own box, so the edge between two rows would
   be painted twice and every rule would come out 2px instead of the
   1px a collapsed table showed. Drawing each edge once (top and
   left per cell, plus the right of the last column and the bottom
   of the last row) keeps the dotted grid exactly as heavy as it
   was. The border colour is unchanged from PART 1.

   `overflow: hidden` clips cell backgrounds to the rounded corner
   (`th` is navy text on white today, but Prism's token classes also
   paint backgrounds, and those would otherwise square the corner
   off). */
table {
  overflow: hidden;
  border-radius: 1rem;
  border-collapse: separate;
  border-spacing: 0;
}
/* One dotted edge per boundary. PART 1 put a full `border` on every
   cell; under `separate` that paints each shared edge twice, so
   every rule came out 2px and the grid looked heavier than before.
   Top and left are drawn on every cell, and the two far edges (the
   right of the last column, the bottom of the last row) are drawn
   once, so the result matches the 1px collapsed table.

   WHY THE RULES HERE ARE 2px, NOT THE 1px PART 1 USED: the reader
   asked for the table's grid to match the dots in the card boxes
   (`.intro`, the deck cards), and those all draw `dotted 2px`.
   A CSS dot has a fixed pitch, so a 1px dot and a 2px dot start
   the same distance apart, but the 2px dot is twice as wide and so
   reads as a roomier, softer rule. Thickness is the only lever
   that moves without inventing a new colour or a background
   gradient, so the grid takes the site's 2px dotted weight. */
th,
td {
  border: none;
  border-top: dotted 2px #999;
  border-left: dotted 2px #999;
}
th:last-child,
td:last-child {
  border-right: dotted 2px #999;
}
tbody > tr:last-child > th,
tbody > tr:last-child > td {
  border-bottom: dotted 2px #999;
}
table > thead > tr:first-child > th:first-child,
table > thead > tr:first-child > td:first-child,
table > tbody > tr:first-child > th:first-child,
table > tbody > tr:first-child > td:first-child {
  border-top-left-radius: 1rem;
}
table > thead > tr:first-child > th:last-child,
table > thead > tr:first-child > td:last-child,
table > tbody > tr:first-child > th:last-child,
table > tbody > tr:first-child > td:last-child {
  border-top-right-radius: 1rem;
}
table > tbody > tr:last-child > th:first-child,
table > tbody > tr:last-child > td:first-child {
  border-bottom-left-radius: 1rem;
}
table > tbody > tr:last-child > th:last-child,
table > tbody > tr:last-child > td:last-child {
  border-bottom-right-radius: 1rem;
}

/* Wide scoreboards (sysop, multi-metric evals): on a phone, ten
   columns cannot share 320px. Wrap them in .table-scroll so the
   page stays put and the board scrolls sideways. min-width keeps
   cells readable instead of table-layout:fixed crushing them.
   ------------------------------------------------------------ */
.table-scroll-hint {
  display: none;
  margin: -0.4rem 0 0.6rem;
}
@media (max-width: 700px) {
  .table-scroll-hint {
    display: block;
  }
}
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 1.6rem 0;
  max-width: 100%;
  border-radius: 1rem;
}
.table-scroll > table {
  margin: 0;
  width: 100%;
  min-width: 44rem;
  table-layout: auto;
}
.table-scroll .results-table td:first-child,
.table-scroll .results-table th:first-child {
  white-space: nowrap;
}
@media (max-width: 700px) {
  .table-scroll {
    /* Slight inset so the dotted edge does not kiss the screen. */
    margin-left: -0.15rem;
    margin-right: -0.15rem;
  }
  .table-scroll > table {
    font-size: 0.85rem;
  }
  .table-scroll > table th,
  .table-scroll > table td {
    padding: 0.35rem 0.45rem;
    overflow-wrap: normal;
    word-break: normal;
  }
}

/* Tables must stay inside the column. main uses overflow-x: hidden,
   so a wide two-column facts table gets clipped on phones instead of
   scrolling. Fixed layout + wrap keeps words inside the frame.
   .facts (front-page Quick facts, and similar key/value tables)
   stacks label above value under 700px so long Community/Email
   lines are not squeezed off the right edge. */
table {
  max-width: 100%;
  table-layout: fixed;
  box-sizing: border-box;
}
th,
td {
  overflow-wrap: anywhere;
  word-break: break-word;
  box-sizing: border-box;
}
@media (max-width: 700px) {
  th,
  td {
    padding: 0.4rem 0.55rem;
  }
  /* Stack .facts label-above-value on phones. Cell-by-cell borders
     and corner radii break once every cell is display:block (the
     outer frame and rounded corners disappear). Draw the frame on
     the table itself; use a single divider under each value. */
  .facts {
    display: block;
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
    border: dotted 2px #999;
    border-radius: 1rem;
    overflow: hidden;
    border-collapse: separate;
    border-spacing: 0;
  }
  .facts tbody,
  .facts tr {
    display: block;
    width: 100%;
  }
  .facts th,
  .facts td {
    display: block;
    width: 100%;
    box-sizing: border-box;
    /* Clear the desktop separate-model edges and corner radii. */
    border: none !important;
    border-radius: 0 !important;
  }
  .facts th {
    padding-bottom: 0.15rem;
  }
  .facts td {
    padding-top: 0.15rem;
    border-bottom: dotted 2px #999 !important;
  }
  .facts tr:last-child td {
    border-bottom: none !important;
  }
  .facts tr:first-child > th,
  .facts tr:first-child > td {
    padding-top: 0.9rem;
  }
  .facts tr:last-child > th,
  .facts tr:last-child > td {
    padding-bottom: 0.9rem;
  }
}


/* A general-purpose centring helper, for a single line of text
   that should sit centred without becoming a heading or a tag
   row (e.g. a closing "find me at..." line). */
.center {
  text-align: center;
}

/* A simple two-column layout. Stacks on narrow screens. */
.two-col {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2rem;
}
@media (max-width: 700px) {
  .two-col { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------
   Card: a resume section in a bubble.

   WHY: the resume ran four loose `h2` sections down one column,
   so "The desk", "Experience", "Skills" and "Education" read as
   one undifferentiated scroll. A card gives each unit the same
   rounded dotted frame the site already uses everywhere else
   (`.intro`, `.entry`, the deck cards), so the page picks up
   the house frame instead of inventing a new one.

   WHY ONE CARD PER JOB AND NOT ONE PER SECTION: a section card
   around all five jobs is just a box around the old list, and
   the five roles still read as one block. A card per role makes
   each job a discrete thing, the same way a front-page project
   gets its own `.entry`. Skills go further and float as four
   peers (see `.card-grid`).

   WHY IT WEARS `.entry`'s FRAME AND NOT ITS OWN: a resume role
   and a front-page project write-up are the same object at two
   sizes, so the frame is declared once (see the `.entry` rule
   below, which now carries both selectors). Only the heading
   reset differs, and that is all that is written here.

   The heading rule cancels the `h3` top margin PART 2 sets:
   inside a frame the padding already does that spacing, and the
   first child's margin would otherwise push the heading down
   from the border by a different amount on every card. The `h2`
   case is kept because a card may open with a section heading
   on a page that has no `h2` above it.
   ------------------------------------------------------------ */
.card > h2:first-child,
.card > h3:first-child { margin-top: 0; }
.card > *:last-child { margin-bottom: 0; }

/* Floating skills cards: two bubbles per row.

   WHY TWO AND NOT FOUR: at four across, a 900px column gives each
   card roughly 200px, and the longer entries ("Web app security &
   performance", "Ollama, Open WebUI, LibreChat") wrapped mid-phrase
   into three and four lines while the neighbours sat half empty.
   Two across gives each card about 430px, so every entry fits on
   one line and the four cards read as two tidy rows of two.

   WHY A GRID AND NOT `.two-col`: `.two-col` was two full-width
   columns of stacked halves, which is just the old two-column list
   with borders around it. A grid of four cards makes each group a
   discrete thing, and the pairing is explicit rather than implied
   by column order.

   WHY THE CARDS DROP THEIR OWN BOTTOM MARGIN: the grid's `gap`
   does the spacing between cards; a per-card margin would add to
   it and make the rows drift apart.

   WHY `align-items: stretch` (the grid default) AND NOT `start`:
   the four groups have different bullet counts (four, four, four,
   five), so with `start` each card ended at its own last line and
   the columns finished at different heights. That read as ragged
   rather than deliberate. Stretching makes the cards fill their
   row, so the dotted frames line up.

   WHY `grid-auto-rows: 1fr` AS WELL: stretching alone equalises
   the two cards in a row, but row 1 and row 2 are still sized
   independently, so the "AI & Data" card (five bullets) would
   leave row 2 taller than row 1. An `fr` row in an auto-height
   grid resolves to the tallest row's content, so all four cards
   come out the same height and the 2x2 block is a true set. */
.card-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: 1fr;
  gap: 1.2rem;
  margin-bottom: 1.8rem;
}
.card-grid .card {
  margin: 0;
}
@media (max-width: 700px) {
  .card-grid { grid-template-columns: 1fr; }
}

/* ------------------------------------------------------------
   Deck: masonry of session parts (TechKnowFile shape, this
   site's dotted language).

   WHY NOT A TABLE: `th, td` box every cell. A talk with 4-18
   parts then looks like a spreadsheet.

   WHY NOT `.two-col`: that is one pair. A deck has many cards
   of uneven height (a slide thumb vs a title-only part).
   `column-count` packs them like masonry. `break-inside:
   avoid` keeps a card from splitting across columns.

   Each card is an `a` so the whole tile is the click, the same
   inversion as `a.tag:hover` (navy fill, white type). Inside
   we reuse `figure`, `.tag`, `h3`, `.meta`. No new colours.
   ------------------------------------------------------------ */
.deck {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.8rem 1.6rem;
  margin: 2rem 0;
  text-align: center;
}
/* Four cards that should sit 2x2, not a lonely fourth under three. */
.deck--two {
  grid-template-columns: repeat(2, 1fr);
}
@media (max-width: 700px) {
  .deck { grid-template-columns: repeat(2, 1fr); }
}
.footer-generated {
  margin: 0.2rem 0 0;
}
.footer-generated a {
  color: var(--muted);
  border-bottom: dotted 1px var(--border);
}
.footer-generated a:hover {
  background-color: var(--navy);
  color: white;
}
@media (max-width: 480px) {
  .deck { grid-template-columns: 1fr; }
}
.deck > a,
.deck > .deck-card {
  display: block;
  padding: 1.3rem 1rem 1.4rem;
  border: dotted 2px var(--border);
  border-radius: 1rem;
  text-decoration: none;
  color: var(--ink);
  text-align: center;
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
/* Static glossary / info cards: same frame as deck links, no jump. */
.deck > .deck-card {
  box-sizing: border-box;
}
.deck > a:hover {
  background-color: transparent !important;
  color: var(--ink) !important;
  transform: translateY(-4px);
  box-shadow: 0 4px 12px rgba(30, 55, 101, 0.12);
}
.deck > a h3,
.deck > .deck-card h3 {
  text-align: center;
  margin: 0.35rem 0 0;
  font-size: 1rem;
}
.deck > .deck-card p {
  margin: 0.45rem 0 0;
  color: var(--muted);
  font-size: 0.95rem;
  text-align: center;
}
.deck > a .tag-row {
  text-align: center;
  margin: 0.2rem 0 0;
}
.deck > a .tag {
  font-size: 0.8rem;
  padding: 0.15rem 0.6rem;
  margin: 0;
}
.deck > a .meta {
  margin: 0.2rem 0 0;
  text-align: center;
}
.deck .card-icon {
  display: block;
  font-size: 3.6rem;
  line-height: 1.15;
  text-align: center;
  margin: 0.35rem 0 0.15rem;
}
.deck > a figure {
  margin: 0 0 0.4rem;
}
.deck > a figure img {
  border: none;
  width: 100%;
  display: block;
  margin: 0 auto;
  border-radius: 0.6rem;
}

/* ------------------------------------------------------------
   Entry: a write-up inside the same bubble as a deck card.

   WHY THIS EXISTS: the front page listed ten projects as deck
   cards, then repeated each one below as loose prose separated
   by `hr`. The cards looked like tiles and the write-ups looked
   like nothing, so the thing you clicked and the thing you got
   did not match. An entry is a card's frame with a page's
   insides: same dotted 2px border, same 1rem radius, same
   `--border` colour, so no new colours and no new divider
   language.

   WHY NOT REUSE `.deck > a`: a deck card is a click, so it is
   centred, it is one third of a row, and its type is small. An
   entry is a block of reading, so it is full width and flush
   left. Only the frame is shared, and a shared frame is exactly
   what was wanted.

   WHY NOT `.note`: `.note` is already the callout box (dotted
   navy, 1rem radius, 0.95rem type) and it is used on this page
   for the intro callout. Two different things must not
   wear the same box, or the callout stops standing out.

   `scroll-margin-top` keeps a card's jump from landing with the
   heading flush against the top edge of the viewport.

   The heading rule cancels the `h3` top margin PART 2 sets:
   inside a frame the padding already does that spacing, and the
   first child's margin would otherwise push the heading down
   from the border by a different amount on every entry.

   WHY `.card` IS IN THIS SELECTOR TOO: the resume's section
   bubbles want this exact frame (same dotted 2px, same radius,
   same padding), and the two are the same object at two sizes.
   Declaring the frame once here keeps them from drifting apart;
   the resume only overrides the heading reset, since its cards
   open with an `h2` rather than an `h3`.
   ------------------------------------------------------------ */
.entry,
.card {
  border: dotted 2px var(--border);
  border-radius: 1rem;
  padding: 1.4rem 1.8rem 1.6rem;
  margin: 0 0 1.8rem;
  scroll-margin-top: 1rem;
}
.entry > h3:first-child { margin-top: 0; }
.entry > *:last-child { margin-bottom: 0; }

/* The `meta` line under an entry heading is that write-up's subtitle.

   WHY IT IS BIGGER THAN `.meta`: PART 2 sizes `.meta` at 0.85rem for
   a byline on a blog post (date, author) where it is a footnote to
   the title. Inside an entry it is the one-line summary the card
   above promised, so at 0.85rem it read as a footnote to a card
   whose heading is 1.15rem. Promoted to 1rem: still below the
   heading, clearly above the 1rem body, so the three steps read
   heading, subtitle, prose.

   WHY NAVY AND ITALIC: grey at any size still reads as small print.
   Navy ties the line to the heading it belongs to (the site's only
   accent, no new colour), and italic keeps it a subtitle rather than
   a second heading. The 1rem size is what makes it legible; the
   colour is what makes it look deliberate.

   The top margin is left smaller than the heading's own 0.6rem
   bottom margin on purpose: the two collapse to 0.6rem, so the
   subtitle stays closer to its heading than to the prose below.

   `.card` shares the rule for the same reason it shares the
   frame: a resume role's date/employer line is the same subtitle
   in the same box, and it must not drop back to the 0.85rem grey
   footnote the PART 2 default makes it. */
.entry > .meta,
.card > .meta {
  font-size: 1rem;
  font-style: italic;
  color: var(--navy);
  margin: 0.3rem 0 1rem;
}

/* Code blocks: wrap long lines instead of scrolling sideways.
   PART 1 sets `overflow-x: auto` on pre, and Prism's own theme
   forces `white-space: pre` on highlighted blocks, so both have
   to be overridden. !important is needed because the Prism
   stylesheet is loaded after this one and would otherwise win.
   `overflow-wrap: anywhere` also breaks very long unbroken
   tokens (URLs, hashes) so nothing can spill out of the box. */
pre,
pre[class*="language-"],
code[class*="language-"],
pre[class*="language-"] code {
  white-space: pre-wrap !important;
  overflow-wrap: anywhere;
  word-break: break-word;
  overflow-x: hidden;
}

/* Add margin to language-specific code blocks for breathing room */
pre[class*="language-"] {
  margin: 1.4rem 0 !important;
}

/* ------------------------------------------------------------
   Back to top: a floating arrow pinned to the right-hand edge.

   WHY IT IS FIXED AND NOT IN THE FLOW: it has to be reachable
   from the bottom of a long page (some regex posts run thousands
   of pixels), and it must not push the 900px column around. It
   is `position: fixed`, so it takes no space in the layout and
   nothing above or below it shifts.

   WHY IT WEARS THE TAG BUBBLE: the site already has exactly one
   round dotted control, the `.tag` pill, and its hover inverts to
   navy with white type. The arrow is the same pill in the same
   colours (dotted 2px `--border`, 1rem radius, navy glyph) so it
   reads as part of the site rather than as a widget bolted on.
   No new colour is introduced anywhere.

   WHY IT IS HIDDEN WITH `visibility` AND NOT `display`: `display:
   none` cannot be transitioned, so the arrow would pop in and out.
   `visibility` transitions, and it still removes the arrow from
   the tab order while hidden, which `opacity: 0` alone would not.
   The pointer-events switch stops the invisible arrow from
   swallowing clicks meant for the page behind it.

   `right` is kept small on purpose: on a phone the 900px column
   fills the screen, so the arrow has to sit over the text rather
   than beside it, and a tight offset keeps it over the margin. */
.to-top {
  position: fixed;
  right: 1.4rem;
  bottom: 1.4rem;
  z-index: 10;

  width: 3rem;
  height: 3rem;
  display: flex;
  align-items: center;
  justify-content: center;

  border: dotted 2px var(--border);
  border-radius: 1rem;
  background-color: white;
  color: var(--navy);
  font-size: 1.4rem;
  line-height: 1;

  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease, visibility 0.25s ease, transform 0.25s ease,
              background-color 0.22s ease, color 0.22s ease, border-color 0.22s ease;
}

/* Shown once the script sees more than one screen of scroll. */
.to-top.is-visible {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
}

/* The same inversion `.tag:hover` uses: navy fill, white type,
   border to match so no dotted ring is left behind. PART 1 gives
   `a:hover` a navy background too, but it does not remove the
   dotted bottom border `a` carries, so it is cleared here. The
   lift matches `.deck > a:hover`, so the two floating things on
   the site move the same way. */
.to-top:hover {
  background-color: var(--navy);
  border-color: var(--navy);
  color: white;
  border-bottom: dotted 2px var(--navy);
  transform: translateY(-4px);
}

/* The glyph is decorative and `aria-hidden` in the markup, so it
   must not pick up the link's hover treatment a second time. */
.to-top span {
  border-bottom: none;
  background: none;
}

@media (max-width: 480px) {
  .to-top {
    right: 0.8rem;
    bottom: 0.8rem;
    width: 2.6rem;
    height: 2.6rem;
    font-size: 1.2rem;
  }
}

/* ------------------------------------------------------------
   First-load twirl.

   WHY `@media (scripting: enabled)` AND NOT A DEFAULT HIDE:
   if the page were `opacity: 0` in ordinary CSS, anyone with
   JavaScript off (or a broken script) would get a blank page.
   The scripting media query is true only when JS is on, so the
   no-JS paint is the ordinary visible page. There is no spinner
   markup: the ring is `html::before`, so it does not exist in
   the HTML and cannot linger as a dead element.

   WHY A 3s FAILSAFE ANIMATION: if JS is on so the hide applies,
   but `site.js` never runs (blocked, 404), `html.is-ready` never
   arrives. The `load-reveal` animation forces the page on after
   1.5 seconds instead of leaving a blank tab.

   WHY THE RING IS DOTTED NAVY: it is the same dotted 3px `--navy`
   circle language as the tag pills, just spinning. No new colour,
   no GIF, no extra file.

   Reduced-motion users skip the hide and the spin: the override
   below puts opacity back to 1 and drops the pseudo-element.
   ------------------------------------------------------------ */
@keyframes load-twirl {
  to { transform: rotate(360deg); }
}
@keyframes load-reveal {
  to { opacity: 1; }
}
@media (scripting: enabled) {
  html:not(.is-ready)::before {
    content: "";
    position: fixed;
    top: 50%;
    left: 50%;
    z-index: 20;
    width: 5.5rem;
    height: 5.5rem;
    margin: -2.75rem 0 0 -2.75rem;
    border: dotted 10px var(--navy);
    border-radius: 50%;
    pointer-events: none;
    animation: load-twirl 0.35s linear infinite;
  }
  html:not(.is-ready) body {
    opacity: 0;
    animation: load-reveal 0.2s ease 1.5s forwards;
  }
  html.is-ready body {
    opacity: 1;
    animation: none;
    transition: opacity 0.18s ease;
  }
}
@media (scripting: enabled) and (prefers-reduced-motion: reduce) {
  html:not(.is-ready)::before {
    content: none;
    animation: none;
  }
  html:not(.is-ready) body {
    opacity: 1;
    animation: none;
  }
}


/* Score tables: auto columns so long model names fit.
   Padding matches every other table. */
.results-table {
  table-layout: auto;
  font-size: 0.9rem;
}
.results-table td:first-child {
  white-space: nowrap;
}
