/* =============================================================================
   YOUSSEF BOUTALEB — PORTFOLIO DESIGN SYSTEM
   -----------------------------------------------------------------------------
   A classic academic stylesheet in the lineage of the orderedlist "Minimal"
   theme this site was forked from — sticky identity rail on the left, a plain
   document on the right, Noto Sans throughout, one blue for links, hairlines
   for structure. Nothing decorative, nothing animated, nothing that will look
   dated in five years, because it did not look current in the first place.

   What this rewrite adds to the original is discipline, not style: every value
   is a token, every repeated pattern is a named component, and the classic
   look is now reproducible instead of hand-tuned per page.

   Contents
     01. Fonts
     02. Design tokens
     03. Reset & base elements
     04. Typography primitives
     05. Layout
     06. Component: sidebar
     07. Component: navigation
     08. Component: page header
     09. Component: block (section)
     10. Component: entry (the universal content record)
     11. Component: definition list
     12. Component: tag & medal
     13. Component: icon
     14. Component: site footer
     15. Responsive
     16. Motion & accessibility preferences
     17. Print

   Rules of the system
     - Every value comes from a token. No literals in component rules.
     - Structure is headings, hairlines and whitespace. No cards, no shadows
       (one exception: the portrait), no gradients, no fills.
     - One typeface, two weights (400 / 700) — the family ships no others.
     - Naming is BEM-ish: .block, .block__element, .block--modifier.
     - Presentation lives here. Content lives in src/pages/. Never inline styles.
   ========================================================================== */


/* 01. FONTS ================================================================ */

@font-face {
  font-family: 'Noto Sans';
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src: local('Noto Sans'),
       url('../fonts/Noto-Sans-regular/Noto-Sans-regular.woff2') format('woff2'),
       url('../fonts/Noto-Sans-regular/Noto-Sans-regular.woff') format('woff'),
       url('../fonts/Noto-Sans-regular/Noto-Sans-regular.ttf') format('truetype');
}

@font-face {
  font-family: 'Noto Sans';
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  src: local('Noto Sans Bold'),
       url('../fonts/Noto-Sans-700/Noto-Sans-700.woff2') format('woff2'),
       url('../fonts/Noto-Sans-700/Noto-Sans-700.woff') format('woff'),
       url('../fonts/Noto-Sans-700/Noto-Sans-700.ttf') format('truetype');
}

@font-face {
  font-family: 'Noto Sans';
  font-weight: 400;
  font-style: italic;
  font-display: swap;
  src: local('Noto Sans Italic'),
       url('../fonts/Noto-Sans-italic/Noto-Sans-italic.woff2') format('woff2'),
       url('../fonts/Noto-Sans-italic/Noto-Sans-italic.woff') format('woff'),
       url('../fonts/Noto-Sans-italic/Noto-Sans-italic.ttf') format('truetype');
}

@font-face {
  font-family: 'Noto Sans';
  font-weight: 700;
  font-style: italic;
  font-display: swap;
  src: local('Noto Sans Bold Italic'),
       url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff2') format('woff2'),
       url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.woff') format('woff'),
       url('../fonts/Noto-Sans-700italic/Noto-Sans-700italic.ttf') format('truetype');
}


/* 02. DESIGN TOKENS ======================================================== */

:root {

  /* --- Typeface ---------------------------------------------------------- */
  /* One family. The original theme's restraint is the point — a second
     typeface is what tips an academic page into looking like a magazine. */
  --font-sans: 'Noto Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-mono: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', Consolas,
               'Liberation Mono', 'Courier New', monospace;

  /* --- Type scale -------------------------------------------------------- */
  /* The original theme's proportions, with body raised 14px → 15px. That one
     step is the whole accessibility argument, and it costs nothing visually. */
  --text-xs:   0.6875rem;  /* 11px — tags */
  --text-sm:   0.75rem;    /* 12px — dates, footnotes, "last update" */
  --text-md:   0.8125rem;  /* 13px — navigation, sidebar meta, contact */
  --text-base: 0.9375rem;  /* 15px — body copy */
  --text-lg:   1rem;       /* 16px — page lede, entry titles */
  --text-xl:   1.125rem;   /* 18px — section headings (h3) */
  --text-2xl:  1.5rem;     /* 24px — page title (h1) */
  --text-3xl:  1.75rem;    /* 28px — the name in the sidebar */

  --leading-tight: 1.2;
  --leading-snug:  1.45;
  --leading-normal: 1.6;

  --weight-regular: 400;
  --weight-bold:    700;

  /* --- Palette (primitives) ---------------------------------------------- */
  /* The original theme's greys, kept exactly: a stepped ramp where each
     heading level sits one notch lighter than the one above it. */
  --ink-900: #222222;   /* h1, h4, strong */
  --ink-800: #393939;   /* h2 */
  --ink-700: #494949;   /* h3, secondary prose */
  --ink-600: #373737;   /* body */
  --ink-500: #676767;   /* meta */
  --ink-400: #6b6b6b;   /* muted — the theme's #777 darkened to clear AA */

  --paper:      #ffffff;
  --paper-soft: #f8f8f8;

  --rule:      #e5e5e5;
  --rule-soft: #eaeaea;

  --blue-600: #267cb9;  /* the theme's link blue — 4.6:1 on white */
  --blue-800: #006699;  /* its hover */
  --blue-050: #f0f4f9;

  /* --- Palette (semantic) ------------------------------------------------ */
  /* Components address these, never the primitives above. */
  --color-bg:            var(--paper);
  --color-surface:       var(--paper-soft);
  --color-text:          var(--ink-600);
  --color-heading:       var(--ink-900);
  --color-heading-2:     var(--ink-800);
  --color-heading-3:     var(--ink-700);
  --color-secondary:     var(--ink-500);
  --color-muted:         var(--ink-400);
  --color-border:        var(--rule);
  --color-border-soft:   var(--rule-soft);
  --color-link:          var(--blue-600);
  --color-link-hover:    var(--blue-800);
  --color-focus:         var(--blue-600);
  --color-selection:     var(--blue-050);

  /* --- Status colours (tags only) ---------------------------------------- */
  --status-neutral-bg:  #f4f4f5;
  --status-neutral-fg:  #4b5563;
  --status-neutral-br:  #e4e4e7;

  --status-accent-bg:   #f0f4f9;
  --status-accent-fg:   #1a56db;
  --status-accent-br:   #dbe4f0;

  --status-success-bg:  #edf7ee;
  --status-success-fg:  #1b5e20;
  --status-success-br:  #cde7cf;

  --status-honor-bg:    #fef8ec;
  --status-honor-fg:    #8f5800;
  --status-honor-br:    #fae4be;

  --status-critical-bg: #fcf0f2;
  --status-critical-fg: #86182a;
  --status-critical-br: #f5d0d6;

  /* Geographic scope. The one hue added for the metadata system: it has to be
     unmistakable next to the accent blue a record's type carries, and it must
     not borrow the green that means "verified" or the red that means
     "download" elsewhere on the site. 6.9:1 on its own tint. */
  --status-scope-bg:    #f5f2fb;
  --status-scope-fg:    #5b3fa8;
  --status-scope-br:    #e5dcf5;

  /* --- Medals ------------------------------------------------------------ */
  /* Struck-metal tones, not the saturated yellow/grey of a game badge. The
     ring is a darker shade of the same metal, which is what keeps a 7px disc
     legible on a pale tag and in print. */
  --medal-gold:        #c8971c;
  --medal-gold-ring:   #97700d;
  --medal-silver:      #b4b8be;
  --medal-silver-ring: #868b91;
  --medal-bronze:      #ab7444;
  --medal-bronze-ring: #7f5326;

  /* --- Spacing scale ----------------------------------------------------- */
  /* Built around the theme's 20px block rhythm rather than a generic 8pt grid,
     so the vertical texture of the original page is preserved exactly. */
  --space-1: 0.25rem;   /*  4 */
  --space-2: 0.5rem;    /*  8 */
  --space-3: 0.75rem;   /* 12 */
  --space-4: 1rem;      /* 16 */
  --space-5: 1.25rem;   /* 20 — the block rhythm */
  --space-6: 2rem;      /* 32 */
  --space-7: 2.5rem;    /* 40 — column gap, section separation */
  --space-8: 3.75rem;   /* 60 */

  /* --- Layout ------------------------------------------------------------ */
  --container-max: 1120px;
  --container-fluid: 95%;
  --sidebar-width: 280px;
  --column-gap: var(--space-7);
  --gutter: var(--space-5);
  --measure: 74ch;

  /* --- Border radius ----------------------------------------------------- */
  --radius-sm:   3px;   /* tags */
  --radius-md:   5px;   /* code blocks, images */
  --radius-full: 9999px;

  /* --- Elevation --------------------------------------------------------- */
  /* One shadow, on one element: the portrait. Content is never lifted. */
  --shadow-portrait: 0 2px 8px rgba(0, 0, 0, 0.1);

  /* --- Motion ------------------------------------------------------------ */
  --duration: 150ms;
  --ease: ease;

  /* --- Icon sizes -------------------------------------------------------- */
  --icon-xs: 12px;
  --icon-sm: 15px;
  --icon-md: 18px;
  --icon-lg: 32px;

  /* --- Breakpoints (reference only; CSS cannot use vars in media queries) --
     --bp-lg: 960px   two columns collapse to one   (the theme's own breakpoint)
     --bp-md: 720px   padding tightens, type settles
     --bp-sm: 480px   navigation scrolls horizontally
   ------------------------------------------------------------------------ */
}


/* 03. RESET & BASE ========================================================= */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  padding: var(--space-2) 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: var(--weight-regular);
  line-height: var(--leading-normal);
  -webkit-font-smoothing: antialiased;
}

/* The theme's stepped heading ramp: each level one notch lighter. */
h1, h2, h3, h4, h5, h6 {
  margin: 0 0 var(--space-5);
  color: var(--color-heading);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
}

h2 { color: var(--color-heading-2); }
h3, h4, h5, h6 { color: var(--color-heading-3); }

h1 { font-size: var(--text-2xl); }
h2 { font-size: var(--text-2xl); }
h3 { font-size: var(--text-xl); }
h4 { font-size: var(--text-lg); }

p, ul, ol, dl, table, pre, blockquote { margin: 0 0 var(--space-5); }
p:last-child, ul:last-child, ol:last-child, dl:last-child { margin-bottom: 0; }

img { max-width: 100%; height: auto; display: block; }

a {
  color: var(--color-link);
  text-decoration: none;
  transition: color var(--duration) var(--ease);
}

/* The original turned links bold on hover, which reflowed the sentence under
   the cursor. Colour plus an underline says the same thing and holds still. */
a:hover {
  color: var(--color-link-hover);
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

strong, b { font-weight: var(--weight-bold); color: var(--color-heading); }
em, i { font-style: italic; }
small { font-size: var(--text-sm); }

::selection { background: var(--color-selection); color: var(--color-heading); }

hr {
  border: 0;
  height: 1px;
  background: var(--color-border);
  margin: 0 0 var(--space-5);
}

blockquote {
  border-left: 1px solid var(--color-border);
  padding-left: var(--space-5);
  font-style: italic;
}

code, kbd, samp {
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--color-heading-3);
}

pre {
  font-family: var(--font-mono);
  font-size: var(--text-md);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-2) var(--space-4);
  overflow-x: auto;
}

table { width: 100%; border-collapse: collapse; }

th, td {
  text-align: left;
  padding: var(--space-1) var(--space-3);
  border-bottom: 1px solid var(--color-border);
}

th { color: var(--color-heading-3); font-weight: var(--weight-bold); }


/* 04. TYPOGRAPHY PRIMITIVES ================================================ */

/* Standard bullet list. The classic theme's content is lists and prose, so
   this is the single most-used piece of the system. */
.points {
  margin: var(--space-3) 0 0;
  padding-left: 1.4em;
  max-width: var(--measure);
}
.points > li { margin-bottom: var(--space-2); }
.points > li:last-child { margin-bottom: 0; }
.points .points { margin-top: var(--space-2); }

.prose { max-width: var(--measure); }
.prose p { margin-bottom: var(--space-4); }

/* Marks an outbound link with a trailing arrow. Presentation, not content —
   the original hard-coded the character into the text of every such link. */
.link-external::after {
  content: '\2197';
  display: inline-block;
  margin-left: 0.2em;
  font-size: 0.85em;
  line-height: 1;
}


/* 05. LAYOUT =============================================================== */

.skip-link {
  position: absolute;
  left: var(--space-4);
  top: var(--space-4);
  z-index: 100;
  padding: var(--space-2) var(--space-4);
  background: var(--color-link);
  color: var(--paper);
  font-size: var(--text-md);
  border-radius: var(--radius-md);
  transform: translateY(-250%);
  transition: transform var(--duration) var(--ease);
}
.skip-link:focus {
  transform: translateY(0);
  color: var(--paper);
  text-decoration: none;
}

/* The theme's two-column shape: fixed identity rail, fluid document. */
.layout {
  width: var(--container-max);
  max-width: var(--container-fluid);
  margin: 0 auto;
  padding: 0 var(--gutter);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--column-gap);
}

.content {
  flex: 1;
  min-width: 0;
  padding-bottom: var(--space-8);
}

/* Anchored headings should not land flush against the viewport edge. */
.content [id] { scroll-margin-top: var(--space-5); }


/* 06. COMPONENT: SIDEBAR =================================================== */

/* Identity, credentials, CV and contact details, sticky on every page. Keeping
   this reachable from anywhere in a long document is the most useful thing a
   portfolio can do for someone reading it in order to contact you. */
.sidebar {
  width: var(--sidebar-width);
  flex-shrink: 0;
  position: sticky;
  top: var(--space-5);
  max-height: calc(100vh - var(--space-7));
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-top: var(--space-5);
  font-size: var(--text-md);
  scrollbar-width: thin;
  -webkit-font-smoothing: subpixel-antialiased;
}

.sidebar__updated {
  margin: 0;
  color: var(--color-muted);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}

.sidebar__name {
  display: block;
  margin-bottom: var(--space-5);
  color: var(--color-heading);
  font-size: var(--text-3xl);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
}
.sidebar__name:hover { color: var(--color-link); text-decoration: none; }

.sidebar__portrait {
  width: 150px;
  height: 150px;
  border-radius: var(--radius-full);
  object-fit: cover;
  margin: 0 auto var(--space-5);
  box-shadow: var(--shadow-portrait);
}

.sidebar__role {
  margin: 0 0 var(--space-1);
  color: var(--color-heading);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
}

/* The certification lines under the role. Rendered as plain lines, as the
   theme's tagline block was, but as a list so the grouping is in the markup. */
.credentials {
  margin: 0 0 var(--space-5);
  padding: 0;
  list-style: none;
  color: var(--color-secondary);
  line-height: var(--leading-snug);
}

.sidebar__section { margin-bottom: var(--space-5); }

.sidebar__heading {
  margin: 0 0 var(--space-2);
  color: var(--color-heading-3);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
}

/* "Personal: address" on one line, as the theme had it. */
.contact { margin: 0; padding: 0; list-style: none; }
.contact > li { margin-bottom: var(--space-1); line-height: var(--leading-snug); }
.contact__label { color: var(--color-secondary); }
.contact__value { word-break: break-word; }

/* Social marks in a row. Fixed square boxes so brand logos of any aspect
   ratio line up; the theme sized them in percentages, which did not. */
.social {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.social__link {
  display: inline-flex;
  padding: var(--space-1);
  border-radius: var(--radius-sm);
  opacity: 0.85;
  transition: opacity var(--duration) var(--ease);
}
.social__link:hover { opacity: 1; }


/* 07. COMPONENT: NAVIGATION ================================================ */

/* The theme rendered this as a <table> row, which meant the site's real tables
   inherited its styling and no page could show which one you were on. Same
   look, as a list, with an active state. */
.nav { margin-bottom: var(--space-5); }

.nav__list {
  display: flex;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  list-style: none;
  border-bottom: 1px solid var(--color-border);
}

.nav__item { flex: 1 1 auto; }

.nav__link {
  display: block;
  padding: var(--space-1) var(--space-3) var(--space-2);
  font-size: var(--text-md);
}
.nav__item:first-child .nav__link { padding-left: 0; }

.nav__link[aria-current='page'] {
  color: var(--color-heading);
  font-weight: var(--weight-bold);
}
.nav__link[aria-current='page']:hover { color: var(--color-heading); }


/* 08. COMPONENT: PAGE HEADER =============================================== */

.page-header { margin-bottom: var(--space-5); }

.page-title { margin-bottom: var(--space-3); color: var(--color-heading-2); }

.page-lede {
  max-width: var(--measure);
  color: var(--color-heading-3);
  font-size: var(--text-lg);
  line-height: var(--leading-snug);
}


/* 09. COMPONENT: BLOCK ===================================================== */

/* A block is one section of the document, introduced by an h3 with the light
   underline the original used on its Projects page — applied everywhere now,
   so the pages finally share one section treatment. */
.block { margin-bottom: var(--space-6); }
.block:last-child { margin-bottom: 0; }

.block__title {
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--color-border-soft);
}

.block__intro {
  margin-bottom: var(--space-5);
  max-width: var(--measure);
  color: var(--color-heading-3);
}


/* 10. COMPONENT: ENTRY ===================================================== */

/* The universal content record: a job, a project, a paper, a course, a
   workshop, an award. A bulleted list item, exactly as the original CV pages
   were written — one component now instead of six hand-built variants. */
.entries {
  margin: 0;
  padding-left: 1.4em;
}

.entry { margin-bottom: var(--space-5); }
.entry > :first-child { margin-top: 0; }
.entry:last-child { margin-bottom: 0; }

.entry__title {
  margin: 0;
  color: var(--color-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  line-height: var(--leading-snug);
}
.entry__title a { color: var(--color-link); }

/* The role or venue, trailing the title on the same line at normal weight. */
.entry__role {
  color: var(--color-heading-3);
  font-weight: var(--weight-regular);
}

.entry__period {
  margin: 0;
  color: var(--color-muted);
  font-size: var(--text-sm);
  font-style: italic;
}

.entry__meta {
  margin: var(--space-1) 0 0;
  color: var(--color-heading-3);
  max-width: var(--measure);
}

.entry__summary {
  margin: var(--space-2) 0 0;
  max-width: var(--measure);
}

.entry__group { margin-top: var(--space-3); }

.entry__group-title {
  margin: 0;
  color: var(--color-heading-3);
  font-weight: var(--weight-bold);
}

/* An issuer heading with its brand mark, used to group certifications. */
.issuer {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  color: var(--color-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
}


/* 11. COMPONENT: DEFINITION LIST =========================================== */

/* Skills, languages and other "category: values" content. The original wrote
   these as <li><b>Label:</b> values</li> — correct on screen, but the pairing
   lived only in the punctuation. This renders identically while being a real
   definition list: the bullet, the bold label, the colon and the values all sit
   on one line. The wrapper div is valid inside <dl> and is what lets a dt/dd
   pair share a single list marker. */
.deflist { margin: 0; padding-left: 1.4em; }

.deflist__item {
  display: list-item;
  list-style: disc;
  margin-bottom: var(--space-2);
  max-width: var(--measure);
}
.deflist__item:last-child { margin-bottom: 0; }

.deflist__item > dt {
  display: inline;
  color: var(--color-heading);
  font-weight: var(--weight-bold);
}
.deflist__item > dt::after { content: ':'; }

.deflist__item > dd { display: inline; margin: 0; }

/* Metric-led records: the figure leads, the evidence link closes. */
.impact__source { white-space: nowrap; }


/* 12. COMPONENT: TAG ======================================================= */

/* Tags classify; they never decorate. There are two families and no third.

   METADATA tags describe a record along a fixed set of dimensions. The colour
   belongs to the *dimension*, not to the value, which is the whole point: a
   reader learns once that amber is placement and violet is reach, and from
   then on reads a tag list positionally instead of word by word. Order is not
   set here or in the markup — it comes from META_ORDER in tools/build.py, so
   no page can put scope before type.

   There is one model per page, each with its own categories:

     awards
     .tag--placement — 1st Place, Finalist, 21st Place        (amber + medal)
     .tag--type      — Competitive Programming, Hackathon     (blue)
     .tag--scope     — Regional, National, African            (violet)
     .tag--scale     — 86 teams, 300 participants             (grey)

     workshops
     .tag--format    — Workshop, Workshop Series, Hands-on Lab (blue)
     .tag--mode      — On-site, Online                         (violet)
     .tag--audience  — Engineering Students, Professionals     (amber)
     .tag--host      — OLIVESOFT, Securinets ENIS              (grey)

     teaching
     .tag--level     — Master's Year 1, Master's Year 2        (amber)
     .tag--workload  — 32 h · 20 lecture + 8 lab + 4 project   (blue)
     .tag--scale     — 12 students                             (grey, reused)

     research
     .tag--status     — Published, In Progress                 (amber)
     .tag--authorship — Second Author                          (blue)
     .tag--publisher  — Elsevier                               (grey)

     writing
     .tag--format    — Walkthrough, Configuration Guide         (blue, reused)
     .tag--reach     — 3K views · 1.5K reads                    (violet)
     .tag--platform  — Medium                                   (grey)

     projects
     .tag--upstream  — Submitted upstream · PR #586             (amber, linked)
     .tag--kind      — Kanboard Plugin, Notebook                (blue)
     .tag--stack     — Python · YOLOv8 · FastAPI · Gradio       (grey)

     experience
     .tag--domain    — E-commerce & Retail                      (blue)
     .tag--stack     — Talend · Azure · Datadog · Salesforce    (grey, reused)

     education
     .tag--accreditation — EUR-ACE® Accredited        (grey, reused, linked)

   `scale` is shared rather than duplicated: it means the size of the group a
   record involved on both pages that use it — 86 teams, 12 students — so it
   keeps one name, one meaning and one rule. `format` is shared on the same
   terms: on Workshops and on Writing alike it names the shape the deliverable
   takes — a Hands-on Lab, a Configuration Guide — and so keeps one rule too.

   `publisher` and `platform` are the case that proves the opposite half of
   that test. Both sit last, both are grey, and both answer *who stands behind
   this* — but Elsevier peer-reviewed the work and Medium hosted it, which are
   not the same claim, so they keep separate names and separate models. The
   shared grey is what makes the pair legible down the Research page; the
   separate names are what stops the second block borrowing the first block's
   authority.

   `status` is the case that proves the family rule. Published and In Progress
   are the same amber, because the colour says *this tag is a status* and never
   *this status is the good one*. The earlier hand-written version of the
   Research page gave Published a green and In Progress an amber, which read as
   a verdict on the record rather than as a category.

   UTILITY tags mark a single fact about a record that is not one of those
   dimensions, and are written by hand where they apply:

     .tag--neutral  — factual context (technology, cohort, institution)
     .tag--accent   — role or category
     .tag--success  — verified / published / shipped
     .tag--honor    — distinction or pending status
     .tag--critical — downloadable artefact */
.tag-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-2);
  margin: var(--space-2) 0 0;
  padding: 0;
  list-style: none;
}
.tag-list > li { display: flex; }

.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  padding: 1px 7px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
  font-weight: var(--weight-bold);
  line-height: 1.5;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

/* Metadata family — one dimension, one colour, for the whole of its model.
   The two models are read on separate pages and so may reuse a hue; what may
   never happen is two categories of the *same* model sharing one, because
   that is what lets a tag list be read positionally. */
.tag--placement { background: var(--status-honor-bg);   color: var(--status-honor-fg);   border-color: var(--status-honor-br); }
.tag--type      { background: var(--status-accent-bg);  color: var(--status-accent-fg);  border-color: var(--status-accent-br); }
.tag--scope     { background: var(--status-scope-bg);   color: var(--status-scope-fg);   border-color: var(--status-scope-br); }

.tag--format    { background: var(--status-accent-bg);  color: var(--status-accent-fg);  border-color: var(--status-accent-br); }
.tag--mode      { background: var(--status-scope-bg);   color: var(--status-scope-fg);   border-color: var(--status-scope-br); }
.tag--audience  { background: var(--status-honor-bg);   color: var(--status-honor-fg);   border-color: var(--status-honor-br); }

.tag--level     { background: var(--status-honor-bg);   color: var(--status-honor-fg);   border-color: var(--status-honor-br); }
.tag--workload  { background: var(--status-accent-bg);  color: var(--status-accent-fg);  border-color: var(--status-accent-br); }

.tag--status     { background: var(--status-honor-bg);  color: var(--status-honor-fg);   border-color: var(--status-honor-br); }
.tag--authorship { background: var(--status-accent-bg); color: var(--status-accent-fg);  border-color: var(--status-accent-br); }

/* `reach` takes the violet the family already spends on reach: scope says how
   far an award travelled, mode says how far a room did, and this says how far
   an article did. The hue was not chosen for this category — it was already
   the answer to the question this category asks. */
.tag--reach      { background: var(--status-scope-bg);  color: var(--status-scope-fg);   border-color: var(--status-scope-br); }

/* `upstream` is amber because it is a status, and amber is what a status looks
   like here — the same rule that puts Published and In Progress in one colour
   on Research. It is emphatically NOT green: an open pull request is a
   submission, and the earlier hand-written version of this page styled it with
   the success green, which said "accepted" about work that had not been. The
   tag now states its own state in words and the colour grades nothing. */
.tag--upstream   { background: var(--status-honor-bg);  color: var(--status-honor-fg);   border-color: var(--status-honor-br); }
.tag--kind       { background: var(--status-accent-bg); color: var(--status-accent-fg);  border-color: var(--status-accent-br); }

/* `domain` is blue because it is the substance of the record — the same slot
   `type` holds on an award and `kind` holds on a project. It says what the
   data was about, which the job title beside it does not: two roles both
   called Data Integration Engineer moved e-commerce orders in one case and
   infrared sensor readings in the other. */
.tag--domain     { background: var(--status-accent-bg); color: var(--status-accent-fg);  border-color: var(--status-accent-br); }

/* The last category of each model is context rather than a claim — how large
   the field was, who hosted the room, who published it — so it is the one
   metadata tag that reads at regular weight, and the eye lands on the ones
   before it first.

   `accreditation` joins them rather than taking the utility success-green the
   hand-written Career page gave it. It answers the same question `publisher`,
   `host` and `platform` answer — who stands behind this — and DESIGN.md's rule
   is to reuse the treatment when the question is the same. The green was doing
   something else: it graded the value, telling the reader the accreditation
   was the good news, when the category itself is the information and a degree
   without one simply renders no tag. */
.tag--scale,
.tag--host,
.tag--publisher,
.tag--platform,
.tag--stack,
.tag--accreditation {
  background: var(--status-neutral-bg);
  color: var(--status-neutral-fg);
  border-color: var(--status-neutral-br);
  font-weight: var(--weight-regular);
}

/* Utility family. */
.tag--neutral  { background: var(--status-neutral-bg);  color: var(--status-neutral-fg);  border-color: var(--status-neutral-br); }
.tag--accent   { background: var(--status-accent-bg);   color: var(--status-accent-fg);   border-color: var(--status-accent-br); }
.tag--success  { background: var(--status-success-bg);  color: var(--status-success-fg);  border-color: var(--status-success-br); }
.tag--honor    { background: var(--status-honor-bg);    color: var(--status-honor-fg);    border-color: var(--status-honor-br); }
.tag--critical { background: var(--status-critical-bg); color: var(--status-critical-fg); border-color: var(--status-critical-br); }

a.tag:hover { text-decoration: underline; opacity: 0.85; }
a.tag.link-external::after { margin-left: 0; }

/* The medal disc on a top-three placement. It carries no information the tag's
   own text does not already give — "1st Place" is right beside it — so it is
   aria-hidden and exists only to be recognised before the text is read. This
   is why the placement tag never says "Winner": the medal is the signal and
   the wording stays factual. Drawn in CSS rather than shipped as an image so
   it scales with the type and costs nothing to load. */
.medal {
  flex: none;
  width: 0.7em;
  height: 0.7em;
  border: 1px solid;
  border-radius: var(--radius-full);
}

.medal--gold   { background: var(--medal-gold);   border-color: var(--medal-gold-ring); }
.medal--silver { background: var(--medal-silver); border-color: var(--medal-silver-ring); }
.medal--bronze { background: var(--medal-bronze); border-color: var(--medal-bronze-ring); }


/* 13. COMPONENT: ICON ====================================================== */

/* Brand logos arrive in wildly different aspect ratios — square, 2500x1184
   wordmarks, 412x800 portraits. Every icon therefore renders inside a fixed
   square box with object-fit: contain, which is what keeps them undistorted
   and optically aligned. Icons are never sized inline. */
.icon {
  flex: none;
  display: inline-block;
  width: var(--icon-md);
  height: var(--icon-md);
  object-fit: contain;
  object-position: center;
  vertical-align: -0.2em;
}

.icon--xs { width: var(--icon-xs); height: var(--icon-xs); }
.icon--sm { width: var(--icon-sm); height: var(--icon-sm); }
.icon--md { width: var(--icon-md); height: var(--icon-md); }
.icon--lg { width: var(--icon-lg); height: var(--icon-lg); }

/* An icon-only link (repository, slides) sitting inside a heading. */
.icon-link {
  display: inline-flex;
  padding: 0.1em;
  margin-left: 0.15em;
  border-radius: var(--radius-sm);
  opacity: 0.7;
  transition: opacity var(--duration) var(--ease);
}
.icon-link:hover { opacity: 1; }


/* 14. COMPONENT: SITE FOOTER =============================================== */

/* Provenance, at the foot of the identity rail — where the theme put it. */
.site-footer {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--color-border);
  color: var(--color-muted);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
}
.site-footer p { margin: 0 0 var(--space-2); }
.site-footer p:last-child { margin-bottom: 0; }
.site-footer a { color: var(--color-secondary); }


/* 15. RESPONSIVE =========================================================== */

/* --- ≤960px: two columns become one (the theme's own breakpoint) ---------- */
@media (max-width: 960px) {
  .layout {
    display: block;
    width: auto;
    max-width: none;
  }

  .sidebar {
    position: static;
    width: auto;
    max-height: none;
    overflow: visible;
    text-align: center;
    padding-bottom: var(--space-5);
    margin-bottom: var(--space-5);
    border-bottom: 1px solid var(--color-border);
  }

  /* Contact details and profiles read better left-aligned in a wide band. */
  .sidebar__section { text-align: left; max-width: 32rem; margin-inline: auto; }
  .social { justify-content: center; }
  .site-footer { text-align: center; }

  .content { padding-bottom: var(--space-7); }
}

/* --- ≤720px: padding tightens ------------------------------------------- */
@media (max-width: 720px) {
  :root { --gutter: var(--space-4); }

  body { padding: 0; word-wrap: break-word; }

  .layout { padding-top: var(--space-5); }

  .nav__item { flex: 0 1 auto; }
  .nav__link { padding-inline: var(--space-2); }
  .nav__item:first-child .nav__link { padding-left: 0; }
}

/* --- ≤480px: navigation scrolls ----------------------------------------- */
@media (max-width: 480px) {
  :root { --text-2xl: 1.375rem; --text-3xl: 1.5rem; }

  .nav__list {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* Bleed to the viewport edges so the scroll affordance is visible. */
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
  }
  .nav__list::-webkit-scrollbar { display: none; }
  .nav__link { white-space: nowrap; }

  .sidebar__portrait { width: 120px; height: 120px; }

  .entries, .points, .deflist { padding-left: 1.1em; }
}


/* 16. MOTION & ACCESSIBILITY PREFERENCES =================================== */

@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;
  }
}

@media (prefers-contrast: more) {
  :root {
    --color-text: var(--ink-900);
    --color-secondary: var(--ink-600);
    --color-muted: var(--ink-600);
    --color-border: var(--ink-400);
  }
}


/* 17. PRINT ================================================================ */

/* The portfolio doubles as a printable CV. */
@media print {
  :root {
    --color-text: #000;
    --color-heading: #000;
    --color-heading-2: #000;
    --color-heading-3: #000;
    --color-link: #000;
  }

  body { padding: 0.4in; font-size: 10.5pt; line-height: 1.45; }

  .layout { display: block; width: auto; max-width: none; padding: 0; }

  .nav, .skip-link, .social, .site-footer { display: none !important; }

  .sidebar {
    position: static;
    width: auto;
    max-height: none;
    overflow: visible;
    padding: 0 0 1em;
    margin-bottom: 1.5em;
    border-bottom: 1pt solid #000;
  }
  .sidebar__portrait { display: none; }

  a { text-decoration: none; }
  .entry, .block { page-break-inside: avoid; }
  .block__title, .page-title { page-break-after: avoid; }
  .tag { border-color: #999; }

  /* The metals are the one place on the site where colour carries meaning that
     greyscale would lose, so they are forced through to the printer. */
  .medal { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}
