/* ============================================================
   Module "experience" - sheet 01, experience, education, skills.

   Two blocks, two very different jobs:

   1. THE TIMELINE (top of the section) stays a normal scrolling
      block: reverse chronological, set as a poster list (outlined
      year, role in display type, one hook line, the period drawn
      as a dimension line), with the original rail, the brass
      progress bar drawn on scroll, the dots that light up and the
      "Now" marker.

   2. THE SHEET (.xp-scene, below the timeline) holds Education and
      Skills. js/experience.js pins it and scrubs it with the
      scroll. Everything in this file is written so that the sheet
      is COMPLETE and readable with no JS at all: the animated
      variant only exists under .xp-scene.is-live, a class the
      module adds when it really drives the scene.

   Loaded after styles.css, so every rule here may override it.
   ============================================================ */

/* ------------------------------------------------------------
   1. Timeline: a poster list (vague 7)

   One entry reads in two seconds, top to bottom and left to right:
   the year drawn in outline (like the big "01" behind the stations),
   the company in brass mono, the role in display type, one hook
   line, and the period drawn as a dimension line whose length
   follows the duration. The original rail, its brass progress bar
   (runTimeline() in app.js) and the dots that light up are kept.

   Without JS everything below is the final picture. The arrival
   states only exist under .timeline.tl-armed, a class js/experience.js
   sets when it runs and motion is allowed.
   ------------------------------------------------------------ */

/* A block of this site is one screen or more, never less. */
#experience { min-height: 100svh; }

.xp-layout {
  /* Year column: sized for the full four-digit year, "2026". */
  --xp-yr-fs: clamp(3.2rem, 5vw, 5rem);
  --xp-num-w: calc(var(--xp-yr-fs) * 2.3);
  --xp-gap: clamp(1.25rem, 3vw, 2.75rem);
  --xp-pad: clamp(2rem, 5.2vh, 3.4rem);        /* vertical breath per entry */
  --xp-ease: cubic-bezier(0.215, 0.61, 0.355, 1);   /* power3.out */
  --xp-expo: cubic-bezier(0.16, 1, 0.3, 1);         /* expo.out */
  /* The pinned counter (section 7) lies over the year column of the
     list: both share the same grid cell. */
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}

.xp-layout > * { grid-area: 1 / 1; }

.timeline {
  gap: 0;
  min-width: 0;
}

.timeline-item {
  grid-template-columns: var(--xp-num-w) minmax(0, 1fr);
  column-gap: var(--xp-gap);
  row-gap: 0;
  align-items: start;
  padding: var(--xp-pad) 0 var(--xp-pad) clamp(1.4rem, 2.6vw, 2.5rem);
}

/* The dot sits on the line of the company cartouche. runTimeline()
   reads this computed top to light it exactly when the bar reaches it. */
.timeline-item::before {
  top: calc(var(--xp-pad) + 0.42rem);
  transition: background-color 0.5s ease-out, box-shadow 0.5s ease-out, transform 0.5s ease-out;
}

/* Graduated rule between entries: a hairline with a tick every 24 px
   and a longer one every 120 px, the scale of a drawing sheet. */
.timeline-item::after {
  content: "";
  position: absolute;
  top: 0;
  left: clamp(0.9rem, 1.6vw, 1.5rem);
  right: 0;
  height: 7px;
  background:
    linear-gradient(var(--line), var(--line)) top / 100% 1px no-repeat,
    repeating-linear-gradient(90deg, color-mix(in srgb, var(--ink-soft) 55%, transparent) 0 1px, transparent 1px 120px) top / 100% 7px no-repeat,
    repeating-linear-gradient(90deg, var(--line) 0 1px, transparent 1px 24px) top / 100% 4px no-repeat;
  transform-origin: left center;
  pointer-events: none;
}

/* ---- 1.1 The year, drawn in outline ----
   Always the full year, "2026": the two digits alone did not read as a
   year. It is real text (screen readers get it at every entry). On a
   desktop with motion, the module hides it visually (.has-ticker) and
   the pinned counter of section 7 takes over the column. */

.timeline-item > .timeline-year {
  grid-column: 1;
  grid-row: 1 / span 2;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.15rem;
  padding-top: 0;
  white-space: nowrap;
  user-select: none;
}

.xp-yr {
  display: block;
  font-family: var(--font-display);
  font-size: var(--xp-yr-fs);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
  color: transparent;
  -webkit-text-stroke: 1.25px var(--brass);
  /* Pulled up so the top of the digits meets the company line. */
  margin-top: -0.12em;
  transition: color 0.3s ease-out;
}

/* ---- 1.2 Company, role, hook ---- */

.timeline-role,
.timeline-body { min-width: 0; grid-column: 2; }

.timeline-item .xp-org {
  max-width: none;
  font-family: var(--font-mono);
  font-size: clamp(0.7rem, 0.8vw, 0.78rem);
  font-weight: 500;
  letter-spacing: 0.1em;
  line-height: 1.5;
  text-transform: uppercase;
  color: var(--brass);
}

.xp-org a {
  display: inline-block;
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  padding: 0.2rem 0.15rem;
  margin: -0.2rem -0.15rem;
  transition: color 0.2s ease-out;
}

.xp-org a:hover { color: var(--ink); }

/* Comfortable tap target without changing how it looks. */
@media (pointer: coarse) {
  .xp-org a {
    padding: 0.85rem 0.25rem;
    margin: -0.85rem -0.25rem;
  }
}

.timeline-item h3 {
  margin: 0.35rem 0 0;
  /* The role rises behind its own mask: the h3 clips, the span moves. A
     little inner room so no descender or accent is ever cut. */
  overflow: hidden;
  padding-block: 0.06em 0.1em;
  font-family: var(--font-display);
  font-size: clamp(2.2rem, 4.2vw, 4.2rem);
  font-weight: 800;
  line-height: 0.98;
  letter-spacing: -0.035em;
  color: var(--ink);
  text-wrap: balance;
}

.timeline-item h3 > span { display: block; }

.timeline-item .xp-hook {
  margin-top: clamp(0.7rem, 1.6vh, 1rem);
  max-width: 36em;
  font-size: clamp(1.05rem, 1.3vw, 1.26rem);
  line-height: 1.45;
  color: var(--ink);
  text-wrap: pretty;
}

/* The one thing to remember in each line: ink weight and a brass
   marker under it, like a highlighted note on a drawing. */
.xp-hook b {
  font-weight: 600;
  background: linear-gradient(transparent 64%, color-mix(in srgb, var(--brass) 20%, transparent) 64%);
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

/* ---- 1.3 The period, drawn as a dimension line ---- */

.timeline-item .xp-cote {
  max-width: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.85rem;
  margin-top: clamp(0.8rem, 1.8vh, 1.15rem);
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.03em;
  line-height: 1.5;
  color: var(--ink-soft);
}

/* Length = months x 5.2 % of the column, so ten months of Navy
   training read longer than a two-month summer. */
.xp-dim {
  position: relative;
  flex: 0 0 auto;
  width: calc(var(--m, 2) * 5.2%);
  min-width: 3.25rem;
  height: 17px;
}

/* Extension lines at both ends. */
.xp-dim::before,
.xp-dim::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--line);
}

.xp-dim::before { left: 0; }
.xp-dim::after { right: 0; }

/* The dimension line and its two arrowheads. */
.xp-dim > i {
  position: absolute;
  left: 0;
  right: 0;
  top: 8px;
  height: 1px;
  background: currentColor;
  transform-origin: left center;
  transition: color 0.3s ease-out;
}

.xp-dim > i::before,
.xp-dim > i::after {
  content: "";
  position: absolute;
  top: -4px;
  border: 4.5px solid transparent;
}

.xp-dim > i::before { left: 0; border-left-width: 0; border-right-color: currentColor; }
.xp-dim > i::after { right: 0; border-right-width: 0; border-left-color: currentColor; }

/* Open period (still running): dashed, no start arrow, no end stop. */
.xp-cote.is-open .xp-dim { width: 4.5rem; }
.xp-cote.is-open .xp-dim::after { display: none; }
.xp-cote.is-open .xp-dim > i {
  background: repeating-linear-gradient(90deg, currentColor 0 6px, transparent 6px 10px);
}
.xp-cote.is-open .xp-dim > i::before { display: none; }

/* ---- 1.4 Hover: the hovered entry takes a little color, the year
   fills with a brass wash and the dimension line turns brass. ---- */

@media (hover: hover) and (pointer: fine) {
  .timeline-item:hover .xp-yr,
  .timeline-item:focus-within .xp-yr { color: color-mix(in srgb, var(--brass) 16%, transparent); }
  .timeline-item:hover .xp-dim > i,
  .timeline-item:focus-within .xp-dim > i { color: var(--brass); }
}

/* ---- 1.5 Arrival (js/experience.js, A.0) ----
   Transforms and opacity only. Rhythm of the house motion spec: 0.8 s
   power3.out, cascade 0 / 120 / 220 / 320 ms inside one entry. The
   graduated rule draws first, the year fades in, then company, role, hook
   and the dimension line that draws itself from its start. */

.timeline.tl-armed > .timeline-item::after {
  transform: scaleX(0);
  transition: transform 1s var(--xp-ease);
}
.timeline.tl-armed .xp-yr { opacity: 0; transition: opacity 0.6s ease-out, color 0.3s ease-out; }
.timeline.tl-armed .xp-now,
.timeline.tl-armed .xp-org,
.timeline.tl-armed .xp-hook,
.timeline.tl-armed .xp-cote-label {
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition: opacity 0.8s ease-out, transform 0.8s var(--xp-ease);
}
.timeline.tl-armed h3 > span {
  transform: translate3d(0, 105%, 0);
  transition: transform 0.9s var(--xp-ease) 0.12s;
}
.timeline.tl-armed .xp-dim { opacity: 0; transition: opacity 0.4s ease-out 0.32s; }
.timeline.tl-armed .xp-dim > i { transform: scaleX(0); transition: transform 0.9s var(--xp-ease) 0.36s, color 0.3s ease-out; }

.timeline.tl-armed .xp-org { transition-delay: 0.06s; }
.timeline.tl-armed .xp-hook { transition-delay: 0.22s; }
.timeline.tl-armed .xp-cote-label { transition-delay: 0.62s; }
.timeline.tl-armed .xp-now { transition-delay: 0.9s; }

.timeline.tl-armed > .tl-in::after { transform: none; }
.timeline.tl-armed > .tl-in .xp-yr,
.timeline.tl-armed > .tl-in .xp-dim { opacity: 1; }
.timeline.tl-armed > .tl-in .xp-now,
.timeline.tl-armed > .tl-in .xp-org,
.timeline.tl-armed > .tl-in .xp-hook,
.timeline.tl-armed > .tl-in .xp-cote-label,
.timeline.tl-armed > .tl-in h3 > span,
.timeline.tl-armed > .tl-in .xp-dim > i { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .timeline.tl-armed > .timeline-item::after,
  .timeline.tl-armed .xp-yr,
  .timeline.tl-armed .xp-now,
  .timeline.tl-armed .xp-org,
  .timeline.tl-armed .xp-hook,
  .timeline.tl-armed .xp-cote-label,
  .timeline.tl-armed h3 > span,
  .timeline.tl-armed .xp-dim,
  .timeline.tl-armed .xp-dim > i {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ------------------------------------------------------------
   2. Current role: filled dot + "Now" tag under the year
   ------------------------------------------------------------ */

.timeline-year .xp-now,
.xp-tk-now {
  display: inline-block;
  padding: 0.18rem 0.6rem 0.2rem;
  border-radius: 999px;
  background: var(--brass);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  line-height: 1.45;
  text-transform: uppercase;
}

.timeline-item.is-now::before {
  background: var(--brass);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--brass) 16%, transparent);
}

@media (prefers-reduced-motion: no-preference) {
  .timeline-item.is-now::before { animation: xp-now-pulse 2.6s ease-in-out infinite; }
}

@keyframes xp-now-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brass) 42%, transparent); }
  55%      { box-shadow: 0 0 0 7px color-mix(in srgb, var(--brass) 0%, transparent); }
}

/* ------------------------------------------------------------
   3. Badges row (after the scene)
   ------------------------------------------------------------ */

/* Spacing only (module "rhythm"). The badges used to sit alone between
   two full-screen blocks with 64 px of paper above them and 162 px of
   section padding below: an isolated strip in the middle of a dead
   stretch. Pulled up against the sheet, they now land while the sheet is
   still leaving and read as its last line. */
.certs {
  margin-top: clamp(0.9rem, 1.6vw, 1.35rem);
  padding-top: clamp(0.8rem, 1.4vw, 1.1rem);
  border-top: 1px dashed var(--line);
  align-items: center;
}

.certs span {
  display: inline-flex;
  align-items: center;
  min-height: 2rem;
}

/* ============================================================
   4. EDUCATION AND SKILLS: one drawing that inks itself in place

   Vague 7 (23/09). Deliberately NOT the system of the poster list
   above (no list of entries, no pinned counter, no wave): the whole
   drawing sits still on the paper and its pieces are drawn in turn.

     Education  ITEEM in display type, first traced in brass outline
                then inked; the degree as a cross-section bar cut into
                five years, the three first hatched (the third, the
                current one, in brass); coursework as drawing labels.
     Skills     a nomenclature: five numbered parts in columns, each
                with its balloon, its name and its details. Languages
                read on one shared CEFR scale.

   Static rules first: they ARE the finished drawing, shown without JS,
   without GSAP and under reduced motion. The hidden starting states
   only exist under .xp-scene.is-live (pinned, scrubbed) and
   .xp-scene.is-flow (one reveal per block), two classes set by
   js/experience.js (block B).
   ============================================================ */

/* ---- 4.1 Shell ---- */

.xp-scene {
  position: relative;
  margin-top: clamp(1.5rem, 4vh, 3rem);
}

/* Pinned, the drawing already fills a screen and breathes on its own:
   the section's bottom padding would only lengthen the blank stretch
   between the drawing and the rail. */
#experience:has(.xp-scene.is-live) {
  padding-bottom: 0;
}

/* min-height, never height: a block on this site is one screen or more. */
.xp-scene.is-live {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100svh;
}

.xp-scene-panel {
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.xp-scene.is-live .xp-scene-panel {
  flex: 1 1 auto;
  padding-block: clamp(1rem, 4vh, 2.75rem);
}

.eds {
  position: relative;
  transform-origin: 50% 50%;
}

/* ---- 4.2 Plate heading: mono label, rule to the right edge ---- */

.eds-kick {
  display: flex;
  align-items: center;
  gap: clamp(0.7rem, 1.4vw, 1.1rem);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  line-height: 1.5;
  text-transform: uppercase;
  color: var(--brass);
}

.eds-kick > span { flex: none; }

.eds-rule {
  flex: 1 1 auto;
  height: 1px;
  background: var(--line);
  transform-origin: left center;
}

/* ---- 4.3 Education ---- */

.eds-edu-grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
  gap: clamp(1.5rem, 4.4vw, 3.75rem);
  align-items: start;
  margin-top: clamp(0.9rem, 2.4vh, 1.6rem);
}

.eds-school {
  font-family: var(--font-display);
  font-size: clamp(4rem, min(8.2vw, 13.5vh), 8.4rem);
  font-weight: 800;
  line-height: 0.86;
  letter-spacing: -0.045em;
  color: var(--ink);
}

.eds-school-in {
  position: relative;
  display: inline-block;
  padding-top: 0.03em;
}

.eds-school-ink { display: block; }

/* The brass outline the name is traced with before it is inked. Only the
   animated variants show it. */
.eds-school-out {
  position: absolute;
  inset: 0.03em 0 0 0;
  color: transparent;
  -webkit-text-stroke: 1.25px var(--brass);
  opacity: 0;
  pointer-events: none;
}

.eds-org {
  margin-top: clamp(0.8rem, 2.2vh, 1.25rem);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  line-height: 1.6;
  text-transform: uppercase;
  color: var(--brass);
}

.eds-lede {
  margin-top: 0.6rem;
  max-width: 25em;
  font-size: clamp(1rem, 1.2vw, 1.12rem);
  line-height: 1.5;
  color: var(--ink);
}

/* The cross-section bar. Above it, its overall dimension (arrowheads and
   "5 years"); under it, the year ticks and the "Now" pointer. */
.eds-cut { padding-top: 0.15rem; }

.eds-dim {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.eds-dim > span { flex: none; }

.eds-dim i {
  position: relative;
  flex: 1 1 auto;
  height: 1px;
  background: var(--ink-soft);
}

.eds-dim-l { transform-origin: right center; }
.eds-dim-r { transform-origin: left center; }

.eds-dim-l::before,
.eds-dim-r::after {
  content: "";
  position: absolute;
  top: -3px;
  border-block: 3.5px solid transparent;
}

.eds-dim-l::before { left: 0; border-right: 7px solid var(--ink-soft); }
.eds-dim-r::after { right: 0; border-left: 7px solid var(--ink-soft); }

.eds-bar {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  height: clamp(38px, 4.8vh, 50px);
  margin: 0.45rem 0 0;
  padding: 0;
  list-style: none;
  border: 1px solid var(--ink);
  background: var(--paper);
}

.eds-bar li {
  position: relative;
  display: grid;
  place-items: center;
  border-left: 1px solid var(--ink);
}

.eds-bar li:first-child { border-left: 0; }

/* Section hatching: ink for the years done, brass for the current one. */
.eds-hatch {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(-45deg,
    color-mix(in srgb, var(--ink) 48%, transparent) 0 1px, transparent 1px 7px);
}

.eds-bar .is-now .eds-hatch {
  background: repeating-linear-gradient(-45deg,
    var(--brass) 0 1.5px, transparent 1.5px 6px);
}

.eds-bar span {
  position: relative;
  padding: 0.05rem 0.35rem;
  background: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}

.eds-bar .is-done span { color: var(--ink); }
.eds-bar .is-now span { color: var(--brass); font-weight: 500; }

.eds-ticks {
  position: relative;
  height: 2.9rem;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.04em;
  line-height: 1;
  color: var(--ink-soft);
}

.eds-ticks span {
  position: absolute;
  top: 0.5rem;
  transform: translateX(-50%);
}

.eds-ticks span::before {
  content: "";
  position: absolute;
  top: -0.5rem;
  left: 50%;
  width: 1px;
  height: 0.32rem;
  background: var(--ink-soft);
}

.eds-ticks span:nth-child(1) { left: 0; transform: none; }
.eds-ticks span:nth-child(1)::before { left: 0; }
.eds-ticks span:nth-child(2) { left: 20%; }
.eds-ticks span:nth-child(3) { left: 40%; }
.eds-ticks span:nth-child(4) { left: 60%; }
.eds-ticks span:nth-child(5) { left: 80%; }
.eds-ticks span:nth-child(6) { left: 100%; transform: translateX(-100%); }
.eds-ticks span:nth-child(6)::before { left: auto; right: 0; }

/* "Now" points up at the middle of year 3, one line below the ticks so it
   never collides with 2026 and 2027, even at 320 px. */
.eds-now {
  position: absolute;
  left: 50%;
  top: 1.45rem;
  transform: translateX(-50%);
  padding: 0.2rem 0.5rem 0.22rem;
  border-radius: 999px;
  background: var(--brass);
  color: #fff;
  font-size: 0.56rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.eds-now::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 100%;
  width: 1px;
  height: 1.45rem;
  background: var(--brass);
}

.eds-cap {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
  margin-top: 0.25rem;
}

.eds-cap b {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 1.8vw, 1.6rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--ink);
}

.eds-cap span {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brass);
}

.eds-course { margin-top: clamp(0.8rem, 2.1vh, 1.25rem); }

.eds-mini {
  margin-bottom: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.64rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--brass);
}

/* Drawing labels: thin frame, square corners, paper behind. */
.eds-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.eds-tags li {
  padding: 0.22rem 0.55rem 0.24rem;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: var(--paper);
  font-size: 0.84rem;
  line-height: 1.3;
  color: var(--ink-soft);
  transition: border-color 0.25s ease-out, color 0.25s ease-out;
}

/* ---- 4.4 Skills: the nomenclature ---- */

.eds-sk { margin-top: clamp(1.6rem, 4.4vh, 3rem); }

.eds-parts {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}

.eds-part {
  position: relative;
  min-width: 0;
  padding: clamp(1rem, 2.4vh, 1.5rem) clamp(0.75rem, 1.3vw, 1.2rem) 0;
}

.eds-part:first-child { padding-left: 0; }
.eds-part:last-child { padding-right: 0; }

/* Column rule between two parts, drawn downward on arrival. */
.eds-vline {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  background: var(--line);
  transform-origin: center top;
  transition: background-color 0.25s ease-out;
}

.eds-part:first-child .eds-vline { display: none; }

.eds-part-head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.55rem;
  margin-bottom: 0.75rem;
}

/* Part balloon, as on an assembly drawing. The circle is drawn by its
   stroke (pathLength 100, so the dash values are plain percentages). */
.eds-ball {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  width: 30px;
  height: 30px;
}

.eds-ball svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  transform: rotate(-90deg);
}

.eds-ball circle {
  fill: var(--paper);
  stroke: var(--brass);
  stroke-width: 1.1;
  stroke-dasharray: 100;
  stroke-dashoffset: 0;
  transition: fill 0.25s ease-out;
}

.eds-ball b {
  position: relative;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--brass);
  transition: color 0.25s ease-out;
}

.eds-part-t {
  overflow: hidden;
  padding-bottom: 0.08em;
  margin-bottom: -0.08em;
  font-family: var(--font-display);
  font-size: clamp(1.3rem, 1.7vw, 1.6rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--ink);
}

.eds-part-t span { display: block; }

.eds-part .eds-tags li {
  padding: 0.18rem 0.45rem 0.2rem;
  font-size: 0.8rem;
}

.eds-lines,
.eds-langs {
  margin: 0;
  padding: 0;
  list-style: none;
}

.eds-lines {
  font-size: 0.9rem;
  line-height: 1.4;
  color: var(--ink-soft);
}

.eds-lines li {
  padding: 0.34rem 0;
  border-top: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
}

.eds-lines li:first-child { padding-top: 0; border-top: 0; }

/* Languages: name, level, then a gauge on the shared CEFR scale (seven
   cells, A1 to C2 plus native). --lv = cells filled. */
.eds-langs li { padding: 0 0 0.55rem; }

.eds-lang-n {
  display: block;
  font-size: 0.9rem;
  line-height: 1.3;
  color: var(--ink);
}

.eds-lang-l {
  display: block;
  margin-top: 0.1rem;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  line-height: 1.4;
  text-transform: uppercase;
  color: var(--brass);
}

.eds-gauge {
  position: relative;
  display: block;
  height: 6px;
  margin-top: 0.3rem;
  border: 1px solid var(--line);
  background-color: var(--paper-2);
  background-image: linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: calc(100% / 7) 100%;
  overflow: hidden;
}

.eds-gauge i {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: calc(var(--lv, 0) / 7 * 100%);
  background: var(--brass);
  transform-origin: left center;
}

.eds-scale {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  margin-top: 0.1rem;
  font-family: var(--font-mono);
  font-size: 0.55rem;
  letter-spacing: 0.02em;
  line-height: 1;
  text-align: center;
  color: var(--ink-soft);
}

/* ---- 4.5 Cartouche: the title strip of the drawing ---- */

.eds-cart {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(0.6rem, 1.6vw, 1.4rem);
  margin-top: clamp(1.4rem, 3.6vh, 2.4rem);
  padding-top: 0.65rem;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  line-height: 1.5;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.eds-cart-rule {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--line);
  overflow: hidden;
}

.eds-cart-rule i {
  display: block;
  height: 100%;
  background: var(--brass);
  transform-origin: left center;
}

.eds-cart-ref { color: var(--brass); }

.eds-cart-title {
  overflow: hidden;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.eds-cart-count {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* ---- 4.6 Hover (mouse only, understated) ---- */

@media (hover: hover) and (pointer: fine) {
  .eds-part:hover .eds-ball circle { fill: var(--brass); }
  .eds-part:hover .eds-ball b { color: #fff; }
  .eds-part:hover .eds-vline { background-color: color-mix(in srgb, var(--brass) 55%, var(--line)); }
  .eds-part:hover + .eds-part .eds-vline { background-color: color-mix(in srgb, var(--brass) 55%, var(--line)); }
  .eds-tags li:hover { border-color: var(--brass); color: var(--ink); }
}

/* ---- 4.7 Starting state of the pinned variant ----
   Set by CSS so the very first frame is already the blank drawing,
   before the first scroll callback. */

.xp-scene.is-live .eds-kick > span,
.xp-scene.is-live .eds-org,
.xp-scene.is-live .eds-lede,
.xp-scene.is-live .eds-dim > span,
.xp-scene.is-live .eds-bar,
.xp-scene.is-live .eds-ticks span,
.xp-scene.is-live .eds-now,
.xp-scene.is-live .eds-cap,
.xp-scene.is-live .eds-mini,
.xp-scene.is-live .eds-tags li,
.xp-scene.is-live .eds-lines li,
.xp-scene.is-live .eds-langs li,
.xp-scene.is-live .eds-scale,
.xp-scene.is-live .eds-ball b,
.xp-scene.is-live .eds-cart { opacity: 0; }

.xp-scene.is-live .eds-rule,
.xp-scene.is-live .eds-dim i,
.xp-scene.is-live .eds-cart-rule i,
.xp-scene.is-live .eds-gauge i { transform: scaleX(0); }

.xp-scene.is-live .eds-vline { transform: scaleY(0); }
.xp-scene.is-live .eds-school-out { opacity: 1; clip-path: inset(0 100% 0 0); }
.xp-scene.is-live .eds-school-ink,
.xp-scene.is-live .eds-hatch { clip-path: inset(0 100% 0 0); }
.xp-scene.is-live .eds-ball circle { stroke-dashoffset: 100; }
.xp-scene.is-live .eds-part-t span { transform: translate3d(0, 105%, 0); }

.xp-scene.is-live .eds-school-in,
.xp-scene.is-live .eds-part-t span { will-change: transform; }

/* ---- 4.8 Flow variant: one reveal per block ----
   Screens where a pin would not hold the whole drawing (phones, tablets,
   short windows). js/experience.js adds .eds-blk to each block and .eds-f
   (with --i, its rank) to what arrives in it, then .is-in once the block
   enters the screen. House values: 0.8 s, power3.out, 70 ms cascade. */

.xp-scene.is-flow .eds-f {
  transition:
    opacity 0.8s cubic-bezier(0.215, 0.61, 0.355, 1) calc(var(--i, 0) * 70ms),
    transform 0.8s cubic-bezier(0.215, 0.61, 0.355, 1) calc(var(--i, 0) * 70ms),
    clip-path 0.9s cubic-bezier(0.645, 0.045, 0.355, 1) calc(var(--i, 0) * 70ms),
    stroke-dashoffset 0.8s cubic-bezier(0.215, 0.61, 0.355, 1) calc(var(--i, 0) * 70ms);
}

.xp-scene.is-flow .eds-blk:not(.is-in) .eds-f { opacity: 0; transform: translate3d(0, 14px, 0); }
.xp-scene.is-flow .eds-blk:not(.is-in) .eds-f.eds-fx { opacity: 1; transform: scaleX(0); }
.xp-scene.is-flow .eds-blk:not(.is-in) .eds-f.eds-fc { opacity: 1; transform: none; clip-path: inset(0 100% 0 0); }
.xp-scene.is-flow .eds-blk:not(.is-in) .eds-f.eds-fd { opacity: 1; transform: none; stroke-dashoffset: 100; }
.xp-scene.is-flow .eds-blk .eds-fc { clip-path: inset(0 0 0 0); }

/* ---- 4.9 Responsive ---- */

@media (max-width: 1180px) {
  .eds-part-t { font-size: clamp(1.2rem, 1.9vw, 1.45rem); }
  .eds-lines { font-size: 0.86rem; }
}

/* Below the pin threshold: the parts go on two columns, a hairline on top
   of each instead of the column rules. */
@media (max-width: 999px) {
  .eds-parts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: clamp(1.25rem, 4vw, 2.5rem);
  }

  .eds-part,
  .eds-part:first-child,
  .eds-part:last-child {
    padding: 1.1rem 0 1.4rem;
    border-top: 1px solid var(--line);
  }

  .eds-vline { display: none; }
}

@media (max-width: 760px) {
  .eds-edu-grid { grid-template-columns: minmax(0, 1fr); gap: 1.6rem; }
  .eds-lede { max-width: 32em; }
}

/* Phone: one column, balloon and name on one line. */
@media (max-width: 640px) {
  .eds-school { font-size: clamp(3.3rem, 19vw, 4.6rem); }

  .eds-parts { grid-template-columns: minmax(0, 1fr); }

  .eds-part,
  .eds-part:first-child,
  .eds-part:last-child { padding: 1rem 0 1.2rem; }

  .eds-part-head {
    flex-direction: row;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.7rem;
  }

  .eds-part-t { font-size: 1.45rem; }
  .eds-tags li { font-size: 0.8rem; }
  .eds-lines { font-size: 0.92rem; }

  .eds-cart {
    grid-template-columns: auto auto;
    justify-content: space-between;
    font-size: 0.56rem;
    letter-spacing: 0.1em;
  }

  .eds-cart-title { display: none; }
}

@media (max-width: 380px) {
  .certs span { font-size: 0.72rem; padding: 0.4rem 0.8rem; }

  .eds-org { letter-spacing: 0.1em; }
  .eds-tags { gap: 0.3rem; }
  .eds-tags li { font-size: 0.76rem; padding: 0.18rem 0.42rem; }
  .eds-ticks { font-size: 0.56rem; }
}

/* ------------------------------------------------------------
   6. Poster list on a phone (vague 7): one column, the outlined year
   becomes the header of each entry, the "Now" tag beside it; every
   line stays readable without any hover.
   ------------------------------------------------------------ */
@media (max-width: 640px) {
  .xp-layout { --xp-pad: clamp(1.6rem, 4vh, 2.2rem); }

  .timeline-item {
    grid-template-columns: minmax(0, 1fr);
    padding-left: 1.35rem;
  }

  .timeline-item::before { top: calc(var(--xp-pad) + 1.15rem); }
  .timeline-item::after { left: 0.75rem; }

  .timeline-item > .timeline-year {
    grid-column: 1;
    grid-row: auto;
    flex-direction: row;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.8rem;
  }

  .timeline-role,
  .timeline-body { grid-column: 1; }

  .xp-yr {
    font-size: clamp(2.5rem, 12vw, 3.1rem);
    margin-top: 0;
    -webkit-text-stroke-width: 1px;
  }

  .timeline-item h3 { font-size: clamp(1.8rem, 8.4vw, 2.2rem); line-height: 1.02; }

  .timeline-item .xp-hook { font-size: 1.02rem; }

  /* The label drops under the line when both do not fit on one row. */
  .xp-dim { width: calc(var(--m, 2) * 6%); }
}

@media (max-width: 380px) {
  .timeline-item { padding-left: 1.1rem; }
  .timeline-item .xp-org { letter-spacing: 0.07em; }
  .timeline-item .xp-cote { font-size: 0.7rem; }
}

/* ------------------------------------------------------------
   7. The pinned year counter (vague 7, desktop with motion only)

   One year block for the whole list, in the year column, held at the
   upper third of the screen by position: sticky. "20" is locked, the
   last two digits are wheels that roll to the year of the entry that
   crosses the reading line. The counter lives in the list's own box,
   so it rides in with the first entry, holds while the list scrolls
   and leaves with the last one: it can never reach the section title
   above or the Education and Skills sheet below.

   Only js/experience.js sets .has-ticker (GSAP, ScrollTrigger, no
   reduced motion, at least 900 x 600). Without it the counter does
   not exist (display: none) and each entry shows its own full year.
   The counter is decorative (aria-hidden); each entry's year stays in
   the page as visually hidden text.
   ------------------------------------------------------------ */

.xp-ticker {
  display: none;
  position: relative;
  z-index: 1;
  justify-self: start;
  width: var(--xp-num-w);
  /* ol margin (styles.css) + entry left padding (section 1). */
  margin-left: calc(0.5rem + clamp(1.4rem, 2.6vw, 2.5rem));
  padding-block: calc(var(--xp-pad) - 0.35rem) var(--xp-pad);
  pointer-events: none;
  user-select: none;
}

.xp-layout.has-ticker .xp-ticker { display: block; }

/* The entry years give way: still read by screen readers, no longer drawn. */
.xp-layout.has-ticker .timeline-item .xp-yr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.xp-layout.has-ticker .timeline-year .xp-now { display: none; }

.xp-tk {
  position: sticky;
  top: 28vh;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  /* A label on a drawing breaks the lines it sits on: the graduated rules
     between entries pass behind the counter instead of through it. */
  margin-left: -0.9rem;
  padding: 0.35rem 0 0.45rem 0.9rem;
  background: var(--paper);
  opacity: 0;
  transform: translate3d(0, 14px, 0);
  transition: opacity 0.6s ease-out, transform 0.8s var(--xp-ease);
}

.xp-ticker.is-in .xp-tk { opacity: 1; transform: none; }

.xp-tk-yr {
  display: flex;
  font-family: var(--font-display);
  font-size: var(--xp-yr-fs);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
  color: transparent;
  -webkit-text-stroke: 1.25px var(--brass);
  margin-top: -0.12em;
}

.xp-tk-bump { display: flex; }

/* One digit = one window of 1em. */
.xp-tk-d {
  position: relative;
  display: block;
  height: 1em;
  overflow: hidden;
  padding-right: 0.02em;
}

/* A wheel: the neighbouring digits fade out at the edges of the window
   instead of being cut flat (static mask, never animated). */
.xp-tk-w {
  -webkit-mask-image: linear-gradient(transparent, #000 18%, #000 82%, transparent);
  mask-image: linear-gradient(transparent, #000 18%, #000 82%, transparent);
}

.xp-tk-strip {
  display: block;
  transition: transform 0.9s var(--xp-expo);
}

.xp-tk-strip > span { display: block; height: 1em; }

/* Reading index under the year: a hairline whose brass fill redraws at
   every entry that passes, even when the year stays the same. */
.xp-tk-rule {
  display: block;
  width: 100%;
  height: 1px;
  margin-top: 0.55rem;
  background: var(--line);
  overflow: hidden;
}

.xp-tk-rule > i {
  display: block;
  height: 100%;
  background: var(--brass);
  transform-origin: left center;
}

.xp-tk-now {
  margin-top: 0.6rem;
  opacity: 0;
  transform: translate3d(0, -6px, 0);
  transition: opacity 0.4s ease-out, transform 0.4s var(--xp-ease);
}

.xp-ticker.is-now .xp-tk-now { opacity: 1; transform: none; }


/* 7 bis (23/09): the counter on two lines, larger. "20" above, quieter,
   the two wheels below at full stroke. No notch any more when the year
   stays the same (it read as a jump), and "Now" leaves by fading only. */
.xp-tk-yr {
  display: grid;
  grid-template-columns: auto auto;
  justify-content: end;
  font-size: calc(var(--xp-yr-fs) * 1.45);
  line-height: 0.92;
}
.xp-tk-yr > .xp-tk-d { opacity: 0.42; }
.xp-tk-bump { grid-column: 1 / span 2; justify-self: end; }
.xp-tk-now { transform: none; }
.xp-ticker.is-now .xp-tk-now { transform: none; }


/* 7 ter (23/09): entries the counter has not reached yet are pale. When
   it lands on one, the text lights up from left to right (a static mask
   gradient slid across, the edge is the wave) and a brass glint runs
   along the graduated rule at the top of the entry. Going back up, the
   same edge runs back. Desktop with the counter only. */
.xp-layout.has-ticker .timeline-item > .timeline-role,
.xp-layout.has-ticker .timeline-item > .timeline-body {
  -webkit-mask-image: linear-gradient(90deg, #000 0 33.3%, rgba(0, 0, 0, 0.22) 50% 100%);
  mask-image: linear-gradient(90deg, #000 0 33.3%, rgba(0, 0, 0, 0.22) 50% 100%);
  -webkit-mask-size: 300% 100%;
  mask-size: 300% 100%;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: 100% 0;
  mask-position: 100% 0;
  transition: -webkit-mask-position 1.1s cubic-bezier(0.22, 0.61, 0.36, 1),
              mask-position 1.1s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.xp-layout.has-ticker .timeline-item > .timeline-body { transition-delay: 0.08s; }
.xp-layout.has-ticker .timeline-item.is-read > .timeline-role,
.xp-layout.has-ticker .timeline-item.is-read > .timeline-body {
  -webkit-mask-position: 0 0;
  mask-position: 0 0;
}
.xp-wave {
  position: absolute;
  top: -1px;
  left: calc(clamp(1.4rem, 2.6vw, 2.5rem) + var(--xp-num-w) + var(--xp-gap));
  right: 0;
  height: 2px;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(90deg, transparent, var(--brass), transparent) no-repeat;
  background-size: 28% 100%;
  background-position: -40% 0;
}


/* 7 quater (23/09): no rule under the year, and "Now" hangs under the
   digits without taking height, so at the end of the list the counter
   comes to rest level with the last entry instead of above it. */
.xp-tk { position: sticky; }
.xp-tk-now {
  position: absolute;
  right: 0;
  top: 100%;
  margin-top: 0.35rem;
}


/* 7 quinquies (23/09): the year block rests on the middle of the screen.
   Its height is two lines of 1.45 x --xp-yr-fs at line-height 0.92, plus
   the paper padding: half of that above the centre line. */
.xp-tk { top: calc(50vh - var(--xp-yr-fs) * 1.334 - 0.4rem); }

.xp-ticker.now-off .xp-tk-now { opacity: 0; transition-duration: 0.25s; }

/* Now sits closer to the digits, clear of the next entry's rule. */
.xp-tk-now { margin-top: -0.55rem; }
.xp-tk-yr { transform-origin: 100% 50%; }


/* ITEEM outline (23/09): the display face builds its letters from
   overlapping contours, and the tight tracking makes letters overlap
   each other, so a bare stroke drew the inner boxes of the T and the E.
   Two layers: the stroked text below, the same text filled with paper
   on top (a pseudo copy). The paper covers every inner line at once,
   only the outer edge of the stroke shows. The outline lies under the
   ink, which covers it as it arrives. */
.eds-school-out {
  color: transparent;
  -webkit-text-stroke: 2.5px var(--brass);
  z-index: 0;
}
.eds-school-out::after {
  content: attr(data-t);
  position: absolute;
  inset: 0;
  color: var(--paper);
  -webkit-text-stroke: 0;
}
.eds-school-ink { position: relative; z-index: 1; }
/* The tight -0.045em tracking made the letters almost touch, so two
   neighbouring outlines read as one thick double line. A little air
   between the letters, on both layers, gives each its own clean edge. */
.eds-school { letter-spacing: 0.01em; }


/* Major coursework (23/09): one line that drifts slowly on a loop instead
   of a block of four rows of tags. The list is written twice and the
   track slides by exactly one copy, so the loop has no seam. Edges fade
   out, hover or focus pauses it. Reduced motion: one static wrapped list. */
.eds-marquee {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  padding-block: 2px;
}
.eds-marquee-track {
  display: flex;
  width: max-content;
  animation: eds-marquee 46s linear infinite;
}
.eds-marquee .eds-tags { flex-wrap: nowrap; padding-right: 0.35rem; }
.eds-marquee .eds-tags li { white-space: nowrap; }
.eds-marquee:hover .eds-marquee-track,
.eds-marquee:focus-within .eds-marquee-track { animation-play-state: paused; }
@keyframes eds-marquee { to { transform: translate3d(-50%, 0, 0); } }
@media (prefers-reduced-motion: reduce) {
  .eds-marquee { -webkit-mask-image: none; mask-image: none; }
  .eds-marquee-track { animation: none; width: auto; }
  .eds-marquee .eds-tags { flex-wrap: wrap; }
  .eds-marquee .eds-tags-copy { display: none; }
}
