/* ferndevbox.com — foundation stylesheet.
   Layer order below is deliberate and should be kept:
     1. fonts      self-hosted Inter, no external host
     2. tokens     Nocturne design system, amber accent override
     3. base       resets and element defaults
     4. components ported Nocturne classes (.btn .card .tag .input)
     5. site       classes this page adds on top
     6. motion     keyframes
   Rule for edits: no new hex values, no px the --space-* scale already
   carries, headings never heavier than weight 500. */


/* ═══ 1. Fonts ══════════════════════════════════════════════════════════
   Inter v20 variable, vendored from Google Fonts at install time so no
   request leaves the visitor's browser and the CSP needs no font host.
   latin + latin-ext only: the page carries English and Portuguese
   ("Cásper Líbero", "São Paulo"). Same files the blog serves. */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 200 600;
  font-display: swap;
  src: url(/fonts/inter-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 200 600;
  font-display: swap;
  src: url(/fonts/inter-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


/* ═══ 2. Tokens ═════════════════════════════════════════════════════════
   Nocturne, with the accent moved from its blurple to amber. The ramp is
   written in OKLCH exactly as the design specifies it; every accent use on
   the page resolves through these ten steps, so retuning the brand is a
   ten-line edit here and nowhere else. */

:root {
  /* Amber accent ramp — replaces Nocturne's blurple. */
  --color-accent:     oklch(0.735 0.148 62);   /* ≈ #f0a65a — fills, rules, dots */
  --color-accent-100: oklch(0.965 0.028 66);
  --color-accent-200: oklch(0.915 0.062 64);   /* link hover, active nav link */
  --color-accent-300: oklch(0.845 0.108 63);   /* accent text on dark */
  --color-accent-400: oklch(0.775 0.138 62);
  --color-accent-500: oklch(0.700 0.150 62);
  --color-accent-600: oklch(0.605 0.135 61);
  --color-accent-700: oklch(0.500 0.112 60);
  --color-accent-800: oklch(0.400 0.088 59);   /* .tag-accent background */
  --color-accent-900: oklch(0.300 0.066 58);

  /* Nocturne carries a second accent role. This design is mono, so it is
     pinned to the same ramp rather than removed — that keeps any Nocturne
     component ported later from falling back to purple. */
  --color-accent-2:     var(--color-accent);
  --color-accent-2-100: var(--color-accent-100);
  --color-accent-2-200: var(--color-accent-200);
  --color-accent-2-300: var(--color-accent-300);
  --color-accent-2-400: var(--color-accent-400);
  --color-accent-2-500: var(--color-accent-500);
  --color-accent-2-600: var(--color-accent-600);
  --color-accent-2-700: var(--color-accent-700);
  --color-accent-2-800: var(--color-accent-800);
  --color-accent-2-900: var(--color-accent-900);

  /* Ground and neutrals. --color-page is darker than Nocturne's own
     --color-bg: the page sits on it, cards sit on --color-surface, and the
     gap between the two is what gives the cards their edge without a
     border. */
  --color-page:    #12141f;
  --color-bg:      #161826;
  --color-surface: #232532;
  --color-text:    #e9e9ed;
  --color-divider: color-mix(in srgb, #e9e9ed 16%, transparent);

  --color-neutral-100: #f3f5fe;  /* brightest text, emphasis in bullets */
  --color-neutral-200: #e4e7f5;
  --color-neutral-300: #cfd3e5;  /* body copy inside cards */
  --color-neutral-400: #b2b6ca;  /* secondary text, nav links */
  --color-neutral-500: #9397ab;  /* captions, sublines */
  /* Meta and footer headings. Light enough to clear 4.5:1 against
     --color-page at the 10-12px sizes it is used at; #75798c, the
     design's original, measured 4.25 and failed AA. On a card it is
     still too dark — --color-surface is lighter than the page — so the
     on-card uses below take --color-neutral-500 instead. */
  --color-neutral-600: #7d8195;
  --color-neutral-700: #595d6c;  /* inactive dots, dashed borders */
  --color-neutral-800: #3f424d;  /* hairlines, dividers */
  --color-neutral-900: #292b31;

  /* Label colour on an amber fill. Dark brown rather than black: pure
     black on this amber reads as a hole. */
  --color-on-accent: #17130c;

  --font-heading: "Inter", system-ui, sans-serif;
  --font-heading-weight: 500;
  --font-body: "Inter", system-ui, sans-serif;

  /* Density 0.7x. Section rhythm is calc(var(--space-8) * 4) ≈ 90px. */
  --space-1: 2.8px;
  --space-2: 5.6px;
  --space-3: 8.4px;
  --space-4: 11.2px;
  --space-6: 16.8px;
  --space-8: 22.4px;

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 14px;

  /* Elevation on a dark ground is a hairline edge plus ambient darkness,
     not a soft drop shadow. */
  --shadow-sm: 0 0 0 1px #3f424d;
  --shadow-md: 0 0 0 1px #595d6c, 0 6px 18px rgba(0, 0, 0, 0.55);
  --shadow-lg: 0 0 0 1px #9397ab, 0 16px 40px rgba(0, 0, 0, 0.65);

  /* Page shell — the same measurements every section uses. */
  --shell-max: 1200px;
  --shell-pad: clamp(20px, 5vw, 80px);
  --nav-h: 58px;

  /* Reveal timing. The design's intended values, slower than the
     prototype's 0.68s: a calmer load reads as considered rather than
     eager. Per-section stagger steps stay in the templates on
     data-stagger, which is where the design put them. */
  --reveal-dur: 1.05s;
  --reveal-ease: cubic-bezier(0.2, 0.7, 0.2, 1);
  --section-gap: calc(var(--space-8) * 4);
}


/* ═══ 3. Base ═══════════════════════════════════════════════════════════ */

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

/* --nav-h is the sticky bar's measured height. scroll-padding-top keeps an
   anchor jump from landing the section heading underneath it — without
   this, clicking "Career" hides the heading behind the bar. The bar grows
   on a phone because the section links wrap to their own row, and again on
   a very narrow one where they wrap twice, so the value is restated at
   those two widths. Overshooting is harmless; undershooting hides copy. */
html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + var(--space-6));
}

body {
  margin: 0;
  background: var(--color-page);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.55;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  line-height: 1.12;
  letter-spacing: -0.015em;
  margin: 0 0 var(--space-2);
}
h1 { font-size: 42px; }
h2 { font-size: 32px; }
h3 { font-size: 25px; }
h4 { font-size: 20px; }
h5 { font-size: 16px; }
h6 { font-size: 13px; letter-spacing: 0.08em; text-transform: uppercase; }

p { margin: 0 0 var(--space-3); }

a { color: var(--color-accent-300); text-decoration: none; }
a:hover { color: var(--color-accent-200); }

img { display: block; max-width: 100%; }
figure { margin: 0; }

ul, ol { margin: 0; padding: 0; }

/* Keep the accent focus ring. Every interactive thing on the page relies
   on it, and it is the whole of the keyboard story. */
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

/* Tap area, not appearance. These controls all draw between 20 and 23px
   tall — comfortable with a mouse, marginal with a thumb. The padding grows
   the area that answers a tap to at least 24px; where the growth would move
   the layout, a matching negative margin takes it back out. Nothing on the
   page looks different.

   WCAG 2.2 already passes these on its spacing exception, since none of them
   sits within 24px of its neighbour. This is about the phone rather than the
   checklist, which is why it lives here as one decision instead of being
   scattered through the components it touches.

   The negative margin is deliberately absent from .foot-list a: the rows are
   only 5.6px apart, and pulling 4px back off each would leave neighbouring
   tap areas overlapping, which is worse than a small target. */
/* The horizontal half is capped at 4px because the row's gap shrinks to
   10px at 360, and 6px a side would leave neighbouring tap areas
   overlapping. "AI" stays the narrowest control on the page. */
.nav-link { padding: 4px; margin: -4px; }
.brand    { padding-block: 2px; margin-block: -2px; }
.hero-email,
.tl-link,
.foot-list a { padding-block: 2px; }
.asst-chip { min-height: 24px; }

input::placeholder { color: var(--color-neutral-600); }

/* Native disclosure marker removed; the page draws its own chevron. */
details > summary { list-style: none; cursor: pointer; }
details > summary::-webkit-details-marker { display: none; }
details[open] [data-chev] { transform: rotate(90deg); }


/* ═══ 4. Components — ported from Nocturne ══════════════════════════════
   Class names kept as the design system writes them, so a future design
   update lands as a swap rather than a rename. */

/* — rules —
   A Nocturne signature: freestanding rules fade to transparent at both
   ends over 48px rather than stopping cleanly. Box outlines stay solid. */
.hr {
  height: 1px;
  border: 0;
  margin: var(--space-4) 0;
  background: linear-gradient(to right,
    transparent, var(--color-neutral-800) 48px,
    var(--color-neutral-800) calc(100% - 48px), transparent);
}

/* — buttons — */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  cursor: pointer;
  text-decoration: none;
  font-family: var(--font-body);
  font-weight: var(--font-heading-weight);
  font-size: 14px;
  line-height: 1.2;
  color: var(--color-text);
  background: transparent;
  border: 1px solid transparent;
  padding: var(--space-2) calc(var(--space-3) * 1.2);
  border-radius: var(--radius-md);
}
.btn svg { display: block; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

.btn-primary { color: var(--color-accent); border-color: var(--color-accent); }
.btn-primary:hover  { background: color-mix(in srgb, var(--color-accent) 12%, transparent); color: var(--color-accent); }
.btn-primary:active { background: color-mix(in srgb, var(--color-accent) 22%, transparent); }

.btn-ghost { color: var(--color-accent); padding-inline: var(--space-1); }
.btn-ghost:hover  { background: color-mix(in srgb, var(--color-accent) 10%, transparent); color: var(--color-accent); }
.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }

/* — forms — */
.input {
  width: 100%;
  min-height: 36px;
  padding: 6px 10px;
  font: inherit;
  font-size: 14px;
  color: var(--color-text);
  caret-color: var(--color-accent);
  background: var(--color-surface);
  border: 1px solid var(--color-divider);
  border-radius: var(--radius-md);
}
.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }

/* — cards —
   Nocturne's own padding is var(--space-3); this page always overrides it
   per context (role cards, credential cards, the assistant), so the base
   stays as the system defines it. */
.card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}
.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }

/* — tags — */
.tag {
  display: inline-flex;
  align-items: center;
  font-size: 11px;
  letter-spacing: 0.02em;
  padding: 3px 10px;
  border-radius: calc(var(--radius-md) * 0.75);
}
.tag-accent  { background: var(--color-accent-800);  color: var(--color-accent-100); }
.tag-neutral { background: var(--color-neutral-800); color: var(--color-neutral-100); }
/* background is load-bearing, not tidiness: .asst-chip puts this class on a
   <button>, and a button with no background of its own paints the browser's
   own buttonface — a near-white #efefef, which left amber text on light grey
   at 2.1:1 inside a dark card. */
.tag-outline { background: transparent; border: 1px solid var(--color-accent); color: var(--color-accent); }


/* ═══ 5. Site classes ═══════════════════════════════════════════════════ */

/* The shell every section shares. */
.shell {
  max-width: var(--shell-max);
  margin: 0 auto;
  padding: var(--section-gap) var(--shell-pad) 0;
}

/* Solid amber call to action. The only filled button on the page, used
   once in the nav — that scarcity is what makes it read as the primary
   action, so resist adding a second. */
.btn-cv {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 7px 14px;
  border-radius: var(--radius-md);
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.01em;
  white-space: nowrap;
  transition: background 0.2s ease, transform 0.2s ease;
}
.btn-cv:hover  { background: var(--color-accent-300); color: var(--color-on-accent); transform: translateY(-1px); }
.btn-cv:active { background: var(--color-accent-400); }
.btn-cv svg { flex: none; }

/* Neutral tags become interactive in the capability map. */
.tag-hover {
  transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}
.tag-hover:hover {
  border-color: color-mix(in srgb, var(--color-accent) 60%, transparent);
  color: var(--color-accent-200);
  background: color-mix(in srgb, var(--color-accent) 14%, transparent);
}


/* — the two decorative layers —
   Content sits at z-index 1 between them: the network canvas paints below,
   the grain sits above everything including the nav. The grain is a single
   inline fractal-noise tile, so it costs one repaint and no request. */
.layer-net {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 0;
  display: block;
}
.layer-grain {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 3;
  opacity: 0.055;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>");
}
.page { position: relative; z-index: 1; }


/* — sticky nav —
   Sits above the grain layer (z-index 3) so the blur reads over content.
   The blur is the whole effect: the page ground at 82% lets the network
   canvas move behind the bar rather than disappearing under it. */
.nav-bar {
  position: sticky;
  top: 0;
  z-index: 4;
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  background: color-mix(in srgb, var(--color-page) 82%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--color-neutral-800) 70%, transparent);
}
.nav-inner {
  max-width: var(--shell-max);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-6);
  padding: var(--space-4) var(--shell-pad);
}
.brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 15px;
  letter-spacing: -0.01em;
  color: var(--color-text);
}
.brand:hover { color: var(--color-text); }
.brand svg { flex: none; }
.brand-suffix { color: var(--color-neutral-500); }

.nav-links {
  display: flex;
  align-items: center;
  gap: clamp(10px, 2vw, 26px);
  font-size: 13px;
  letter-spacing: 0.02em;
  margin-left: auto;   /* pushes the links and the CV button to the right */
}
/* Colour is set here and overwritten by the active-section observer in
   site.js. With scripting off every link simply stays neutral, which is
   correct rather than broken. */
.nav-link {
  color: var(--color-neutral-400);
  transition: color 0.3s ease;
}
.nav-link:hover { color: var(--color-accent-200); }

/* — hero — */
.hero {
  padding-top: clamp(52px, 10vw, 132px);
  /* No fill mode. The keyframe starts at opacity 0, and a fill mode would
     hold that start state outside the animation's own run — so anything
     that stopped the animation from playing would leave the hero
     permanently invisible. With no fill, the resting state either side is
     the element's own style, which is visible. There is no delay here, so
     the visible result is identical. */
  animation: fa-rise 1.25s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.hero-eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: calc(var(--space-8) * 1.2);
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--color-neutral-500);
}
.hero-eyebrow::before {
  content: "";
  width: 40px;
  height: 1px;
  background: var(--color-accent);
  display: block;
  flex: none;
}
/* The one place on the page where type is allowed to be enormous. Line
   height under 1 is deliberate: the two lines interlock. */
.hero-name {
  font-size: clamp(52px, 13vw, 168px);
  line-height: 0.86;
  letter-spacing: -0.045em;
  margin: 0;
  color: var(--color-text);
}
.hero-name span { color: var(--color-accent-300); }

.hero-lead {
  display: flex;
  align-items: flex-start;
  gap: var(--space-6);
  flex-wrap: wrap;
  margin-top: calc(var(--space-8) * 1.4);
}
.hero-role {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: clamp(18px, 2.4vw, 27px);
  line-height: 1.25;
  letter-spacing: -0.02em;
  color: var(--color-neutral-100);
  max-width: 30ch;
  margin: 0;
}
.hero-positioning {
  font-size: clamp(15px, 1.5vw, 17px);
  line-height: 1.62;
  color: var(--color-neutral-400);
  max-width: 46ch;
  flex: 1 1 320px;
  text-wrap: pretty;
  margin: 0;
}
.hero-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  margin-top: calc(var(--space-8) * 1.6);
}
.hero-email {
  font-size: 14px;
  color: var(--color-neutral-400);
  margin-left: var(--space-3);
}
.hero-email:hover { color: var(--color-accent-200); }



/* ── Section heading ──────────────────────────────────────────────────
   Icon, title, optional subline. Shared by every section so the rhythm
   between them cannot drift. */

.sec-head {
  display: flex;
  align-items: baseline;
  gap: var(--space-6);
  margin-bottom: calc(var(--space-8) * 1.6);
  flex-wrap: wrap;
}
.sec-head h2 {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: clamp(26px, 3.4vw, 40px);
  letter-spacing: -0.03em;
  margin: 0;
}
.sec-sub { font-size: 13px; color: var(--color-neutral-500); }

.sec-icon {
  align-self: center;
  flex: none;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: var(--radius-md);
  border: 1px solid color-mix(in srgb, var(--color-accent) 40%, transparent);
  background: color-mix(in srgb, var(--color-accent) 9%, transparent);
  color: var(--color-accent-300);
}
.sec-icon svg { display: block; }

/* Shared tag row. Used by capability, timeline, AI and the assistant. */
.tag-row { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* Visible to a screen reader, not on screen. For labels that would be
   redundant sighted but are required for an input to be announced. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}


/* ── Metrics ─────────────────────────────────────────────────────────── */

.metrics-sec { padding-top: calc(var(--space-8) * 3); }

.metrics-rule {
  height: 1px;
  margin-bottom: calc(var(--space-8) * 1.5);
  background: linear-gradient(to right,
    transparent,
    var(--color-neutral-800) 48px,
    var(--color-neutral-800) calc(100% - 48px),
    transparent);
}

/* min(Npx, 100%) rather than a bare Npx: a bare track floor is a hard
   minimum, so on a narrow phone the columns refuse to shrink and push the
   whole page sideways. Capping the floor at the container width lets the
   grid fall to one column instead. Every auto-fit grid below does this. */
.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  gap: calc(var(--space-8) * 1.2) var(--space-8);
}
.metric-value {
  font-family: var(--font-heading);
  font-size: clamp(30px, 3.4vw, 42px);
  font-weight: 500;
  letter-spacing: -0.035em;
  line-height: 1;
  color: var(--color-text);
  /* Tabular figures so the four numbers share a baseline grid. */
  font-variant-numeric: tabular-nums;
}
.metric-value span { font-size: 0.5em; color: var(--color-accent-300); }
.metric-caption {
  font-size: 13px;
  line-height: 1.45;
  color: var(--color-neutral-500);
  margin-top: var(--space-3);
  text-wrap: pretty;
}


/* ── Capability map ──────────────────────────────────────────────────── */

.cap-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(270px, 100%), 1fr));
  gap: var(--space-8);
}
.cap-title {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 15px;
  letter-spacing: 0.02em;
  margin: 0 0 var(--space-4);
  color: var(--color-text);
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.cap-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  color: var(--color-accent-300);
}
.cap-icon svg { display: block; }
.cap-blurb {
  font-size: 13px;
  line-height: 1.5;
  color: var(--color-neutral-500);
  margin: 0 0 var(--space-6);
  text-wrap: pretty;
}


/* ── Career timeline ─────────────────────────────────────────────────── */

/* Each row is its own grid rather than all rows sharing one. The rail
   column is a viewport clamp, not content-sized, so rows still align —
   and the earlier-career rows can sit inside a <details> without a grid
   container cutting across the disclosure. */
.tl-row {
  display: grid;
  grid-template-columns: minmax(0, clamp(66px, 13vw, 132px)) 1fr;
  gap: 0 clamp(14px, 2.4vw, 30px);
}

.tl-rail { padding-top: 3px; text-align: right; }
.tl-date {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 14px;
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
  /* The dates carry a newline in content.json; honour it rather than
     asking the content file to hold a <br>. */
  white-space: pre-line;
}
.tl-date-muted { color: var(--color-neutral-400); }
.tl-rail-label {
  font-size: 10px;
  color: var(--color-neutral-600);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  margin-top: 5px;
}

.tl-entry {
  position: relative;
  padding: 0 0 calc(var(--space-8) * 1.6) clamp(18px, 3vw, 34px);
  border-left: 1px solid var(--color-neutral-800);
}
.tl-dot {
  position: absolute;
  left: -5px;
  top: 5px;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--color-neutral-700);
  transition: background 0.5s ease, box-shadow 0.5s ease;
}
.tl-dot-sm { left: -4px; top: 6px; width: 7px; height: 7px; }

.tl-card {
  padding: calc(var(--space-8) * 1.1);
  transition: border-color 0.3s ease, transform 0.3s ease;
}
.tl-card:hover {
  border-color: color-mix(in srgb, var(--color-accent) 45%, transparent);
  transform: translateY(-2px);
}
.tl-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3) var(--space-4);
  margin-bottom: var(--space-3);
}
.tl-head h3 {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: clamp(19px, 2.1vw, 24px);
  letter-spacing: -0.02em;
  margin: 0;
}
.tl-company { color: var(--color-accent-300); font-size: 15px; }
.tl-loc {
  font-size: 12px;
  /* On a card, so a step lighter than the page-ground meta colour. */
  color: var(--color-neutral-500);
  margin-bottom: var(--space-3);
}
.tl-summary {
  font-size: 15px;
  line-height: 1.62;
  color: var(--color-neutral-300);
  margin: 0 0 var(--space-6);
  max-width: 64ch;
  text-wrap: pretty;
}

.tl-bullets {
  list-style: none;
  margin: 0 0 var(--space-6);
  padding: 0;
  display: grid;
  gap: var(--space-3);
}
.tl-bullets li {
  display: grid;
  grid-template-columns: 13px 1fr;
  gap: var(--space-3);
  font-size: 14px;
  line-height: 1.58;
  color: var(--color-neutral-300);
}
.tl-dash { color: var(--color-accent); }
.tl-bullets strong { color: var(--color-neutral-100); font-weight: 600; }

.tl-clients {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  align-items: center;
  margin-bottom: var(--space-4);
}
.tl-clients-label {
  font-size: 11px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  /* On a card, so a step lighter than the page-ground meta colour. */
  color: var(--color-neutral-500);
}

/* A break entry: no card, quieter type. */
.tl-break { padding-top: var(--space-3); }
.tl-break h3 {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 18px;
  letter-spacing: -0.015em;
  margin: 0 0 var(--space-3);
  color: var(--color-neutral-100);
}
.tl-break p {
  font-size: 14px;
  line-height: 1.62;
  color: var(--color-neutral-400);
  margin: 0 0 var(--space-4);
  max-width: 60ch;
  text-wrap: pretty;
}
.tl-link {
  font-size: 14px;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}


/* ── Earlier-career disclosure ────────────────────────────────────────
   A real <details>. It opens with no JavaScript and is keyboard-operable
   without a handler. The compact list lives inside the <summary>; opening
   the panel hides it and shows the full cards instead, so exactly one of
   the two views is ever visible. */

.tl-earlier > summary {
  list-style: none;
  cursor: pointer;
}
/* Safari still paints its own triangle without this. */
.tl-earlier > summary::-webkit-details-marker { display: none; }

.tl-toggle {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 18px;
  letter-spacing: -0.015em;
  color: var(--color-neutral-100);
  padding: var(--space-3) 0;
  transition: color 0.2s ease;
}
.tl-earlier > summary:hover .tl-toggle { color: var(--color-accent-200); }
.tl-toggle .glyph {
  flex: none;
  color: var(--color-accent);
  transition: transform 0.3s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tl-earlier[open] .tl-toggle .glyph { transform: rotate(90deg); }

/* The label swaps between the two states with no script: the strings are
   data- attributes on the span, and CSS chooses which one to print. */
.tl-toggle-meta {
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent-300);
  font-family: var(--font-body);
}
.tl-toggle-meta::before { content: attr(data-open); }
.tl-earlier[open] .tl-toggle-meta::before { content: attr(data-close); }

.tl-preview {
  display: grid;
  gap: var(--space-4);
  padding: var(--space-4) 0 var(--space-8);
  max-width: 60ch;
}
.tl-earlier[open] .tl-preview { display: none; }
.tl-preview-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-4);
  align-items: baseline;
  padding-bottom: var(--space-3);
  border-bottom: 1px solid color-mix(in srgb, var(--color-neutral-800) 70%, transparent);
}
.tl-preview-row:last-child { border-bottom: 0; padding-bottom: 0; }
.tl-preview-title { font-size: 14px; color: var(--color-neutral-200); }
.tl-preview-company { font-size: 13px; color: var(--color-neutral-500); }
.tl-preview-date {
  font-size: 12px;
  color: var(--color-neutral-600);
  font-variant-numeric: tabular-nums;
}

/* The summary is the focus target, so the ring goes on it, inset slightly
   so it does not sit under the sticky nav. */
.tl-earlier > summary:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}


/* ── Credentials ─────────────────────────────────────────────────────── */

.cred-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
  gap: var(--space-6);
}
.cred {
  padding: var(--space-8);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  transition: border-color 0.3s ease;
}
.cred:hover { border-color: color-mix(in srgb, var(--color-accent) 45%, transparent); }
.cred-top { display: flex; align-items: center; justify-content: flex-end; }

/* ── Logos ───────────────────────────────────────────────────────────────
   Company and certification marks. Every slot is optional: content.json
   naming no file means no element at all, so these rules only ever apply to
   a logo that exists.

   The box is fixed and the image is contained inside it, so marks of wildly
   different proportions — a square badge, a long wordmark — still line up
   down the page instead of each setting its own height.

   They are desaturated at rest. These are other people's brands on a
   personal site; at full strength a row of them competes with the copy and
   starts to read as endorsement. Hovering the row brings the nearby one back
   to full colour, which is enough for anyone checking. Desaturation alone
   does the dimming — the opacity this rule used to carry also faded the chip
   underneath, which turned a white plate a dirty grey.

   Each mark sits on a light chip rather than straight on the page ground.
   Supplied logos arrive in three incompatible flavours and the ground is
   dark, so none of them can be trusted to sit on it unaided:

     - transparent PNG drawn in dark ink (OSF, TBWA, the NSW wordmark in
       eHealth) — near-invisible on #12141f, which is the failure the chip
       exists to fix;
     - opaque white rectangle (Enigma, Yatango, JWT) — legible, but the
       rectangle reads as a mistake against the dark page;
     - opaque dark rectangle (Scyne, IVE, Rock) — the mark is fine, the
       unlit edge of the box is not.

   The chip fixes all three at once. Under a dark-ink mark it supplies the
   light ground the artwork assumes. Under a white-boxed mark it matches the
   box, so the seam disappears. Under a dark-boxed mark it contributes only
   the rim and shadow, which is what gives that box a deliberate edge.

   The credential marks are the same three flavours again — ServiceNow's
   shield is transparent dark ink, PMI's ring and SCRUMstudy's lockup are
   opaque white boxes — so they take the chip too. The exception is the two
   education badges, which were drawn for this page and bring their own
   ground; see .cred-logo-bare below.

   The rim is a hairline of white, not a border: a dark border is invisible
   here, and box-shadow draws the rim without touching layout. The drop
   shadow lifts the chip off the ground so a dark-boxed logo still reads as
   a plate rather than a hole. Do not swap either for a `border` — the slot
   is a fixed 104px and a border would eat into the artwork. */
.tl-logo, .cred-logo, .cap-logo {
  display: block;
  object-fit: contain;
  /* Centred inside the chip, never flush to an edge. It is the chip that
     aligns to the rail; artwork pushed to one side strands bare white on
     the other, which is glaring on a square mark like Rock in a slot this
     wide. */
  object-position: center;
  /* Pure white, not an off-white: Enigma, Yatango and JWT ship as opaque
     #ffffff rectangles, and any other value leaves a seam where their box
     meets the chip. Sampled, not assumed. Restraint comes from the
     desaturation below instead, which does not have that side effect. */
  background: #ffffff;
  padding: 5px 7px;
  border-radius: var(--radius-sm);
  box-shadow:
    0 0 0 1px color-mix(in srgb, #ffffff 12%, transparent),
    0 2px 6px rgb(0 0 0 / 0.42);
  filter: saturate(0.85);
  transition: background 0.3s ease, box-shadow 0.3s ease, filter 0.3s ease;
}

/* .tl-row, not .tl-card: the mark sits in .tl-rail, which is the card's
   sibling rather than its child, so a .tl-card:hover rule can never match
   it. The row is the nearest element that contains both. */
.tl-row:hover .tl-logo,
.cred:hover .cred-logo:not(.cred-logo-bare),
.cap-col:hover .cap-logo {
  background: #ffffff;
  filter: none;
  box-shadow:
    0 0 0 1px color-mix(in srgb, #ffffff 22%, transparent),
    0 3px 10px rgb(0 0 0 / 0.5);
}

/* The rail is right-aligned text, so the chip is pushed right to sit under
   the dates rather than floating away from them. margin-left: auto moves the
   chip; the artwork stays centred within it. */
.tl-logo {
  width: 104px; height: 54px;
  margin: var(--space-3) 0 0 auto;
}

/* A mark with a companyUrl is wrapped in this link. The anchor takes over the
   box and the auto margin so the chip lands exactly where the bare image did,
   and the image inside gives up its own margin — otherwise the two would
   stack and push the chip down by a step.

   Sized rather than left to shrink-wrap: an inline anchor round a block image
   leaves a few pixels of line-height below the chip, which the focus ring
   then draws around and makes look misaligned.

   No hover rule of its own. The chip already lifts on .tl-row:hover, and the
   pointer cursor the anchor brings is the affordance; a second, different
   response to hovering the logo itself would just look like a flicker as the
   cursor crosses the rail. The focus ring is the global :focus-visible. */
.tl-logo-link {
  display: block;
  width: 104px; height: 54px;
  margin: var(--space-3) 0 0 auto;
  border-radius: var(--radius-sm);
}
.tl-logo-link .tl-logo { margin: 0; }

/* Narrower than the 104px used elsewhere, because every issuer's credential
   mark is a badge rather than a wordmark — a shield, a ring, a stacked
   lockup, all of them roughly square. In a 104px slot each one shrinks to
   fit the 56px height and strands forty-odd pixels of bare white beside
   itself, so the chip reads as an empty plate with something small on it.
   At 62px the plate hugs the badge. The marks themselves are cropped to
   their artwork in static/logos, so this padding is the only margin.

   margin-right: auto rather than justify-content: space-between on the row.
   With no logo present the row has a single child, and space-between would
   drop the badge to the left; this keeps the badge hard right either way. */
.cred-logo { width: 62px; height: 56px; margin-right: auto; }

/* The two education marks are drawn for this page rather than supplied by an
   issuer: they carry their own dark ground, their own rounded border and the
   page's own accent. Everything the chip provides, they already have, so it
   is taken away here — a white plate behind a badge that has its own would
   read as a mounting error, and a second border inside a fixed 104px slot
   would crop the artwork.

   They keep their full colour too. The desaturation on the chipped marks is
   there to stop other people's brands reading as endorsement; these are not
   other people's brands. For the same reason there is no hover response —
   nothing is being held back at rest for hovering to release.

   They are authored at exactly 104x56, so they take the full slot width back
   rather than the 62px the chipped badges use, and with no padding they fill
   it edge to edge — object-fit has nothing left to do. */
.cred-logo-bare {
  width: 104px;
  background: none;
  padding: 0;
  box-shadow: none;
  filter: none;
}

.cap-logo { width: 104px; height: 56px; margin-top: var(--space-4); }

@media (prefers-reduced-motion: reduce) {
  .tl-logo, .cred-logo, .cap-logo { transition: none; }
}

/* The chip is a light plate by design and stays light in forced-colours
   mode; without this the rim and the plate are both dropped and the
   dark-ink marks vanish again, which is the one outcome it exists to
   prevent. */
@media (forced-colors: active) {
  .tl-logo, .cred-logo:not(.cred-logo-bare), .cap-logo {
    forced-color-adjust: none;
    background: #ffffff;
    box-shadow: 0 0 0 1px CanvasText;
  }
  /* Kept off the plate, but still exempt from the colour substitution: the
     badge is a picture of a badge, and forcing its ground to Canvas leaves
     white text on white. */
  .cred-logo-bare { forced-color-adjust: none; }
}
.cred-title {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 19px;
  letter-spacing: -0.015em;
  margin: var(--space-2) 0 0;
  text-wrap: pretty;
}
.cred-sub { font-size: 13px; color: var(--color-neutral-400); }
.cred-date {
  font-size: 12px;
  /* On a card, so a step lighter than the page-ground meta colour. */
  color: var(--color-neutral-500);
  /* Pushes the date to the bottom so the dates line up across a row of
     cards whose titles wrap to different heights. */
  margin-top: auto;
  padding-top: var(--space-4);
  font-variant-numeric: tabular-nums;
}


/* ── AI, in practice ─────────────────────────────────────────────────── */

.ai-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: var(--space-8);
  align-items: start;
}
.ai-para {
  font-size: 15px;
  line-height: 1.62;
  color: var(--color-neutral-300);
  margin: 0 0 var(--space-6);
  max-width: 50ch;
  text-wrap: pretty;
}
.ai-tags { margin: 0 0 calc(var(--space-8) * 1.4); }
.ai-sub {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 17px;
  letter-spacing: -0.015em;
  margin: 0 0 var(--space-3);
  color: var(--color-text);
}
.ai-blurb {
  font-size: 14px;
  line-height: 1.62;
  color: var(--color-neutral-500);
  margin: 0 0 var(--space-6);
  max-width: 52ch;
  text-wrap: pretty;
}
.ai-blurb strong { color: var(--color-neutral-200); font-weight: 600; }

.proj-list { display: grid; gap: var(--space-3); }
.proj {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-4);
  align-items: center;
  padding: var(--space-4) var(--space-6);
  border: 1px solid var(--color-neutral-800);
  border-radius: var(--radius-md);
  transition: border-color 0.25s ease, background 0.25s ease;
}
.proj:hover {
  border-color: color-mix(in srgb, var(--color-accent) 50%, transparent);
  background: color-mix(in srgb, var(--color-accent) 7%, transparent);
}
.proj-name {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: 15px;
  color: var(--color-text);
  display: block;
}
.proj-desc { font-size: 13px; color: var(--color-neutral-500); }
.proj-arrow { color: var(--color-accent-300); }


/* ── Assistant panel ─────────────────────────────────────────────────── */

.asst {
  padding: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-height: 440px;
}
.asst-strip {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
  border-bottom: 1px solid var(--color-neutral-800);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-neutral-400);
}
.asst-pulse {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--color-accent);
  animation: fa-blink 2.6s ease-in-out infinite;
}

.asst-log {
  flex: 1;
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  overflow-y: auto;
  max-height: 340px;
}
.asst-msg { display: flex; }
.asst-msg-bot { justify-content: flex-start; }
.asst-msg-user { justify-content: flex-end; }
.asst-bubble {
  max-width: 88%;
  padding: var(--space-4) var(--space-6);
  border-radius: var(--radius-md);
  font-size: 14px;
  line-height: 1.6;
  text-wrap: pretty;
  /* anywhere rather than break-word: only anywhere shrinks the bubble's
     min-content width, and it is that width a flex row measures. A pasted
     job description containing one very long unbroken string would
     otherwise widen the card and push the whole page sideways. */
  overflow-wrap: anywhere;
}
.asst-msg-bot .asst-bubble {
  background: color-mix(in srgb, var(--color-neutral-800) 55%, transparent);
  border: 1px solid var(--color-neutral-800);
  color: var(--color-neutral-200);
}
.asst-msg-user .asst-bubble {
  background: color-mix(in srgb, var(--color-accent) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--color-accent) 38%, transparent);
  color: var(--color-accent-100);
  /* Pasted text keeps its line breaks. A job description arrives with
     structure and reads as nonsense reflowed into one paragraph. */
  white-space: pre-wrap;
}

/* The bubble standing in while an answer is being found. Dashed and italic
   so it reads as provisional rather than as something that was said. */
.asst-msg-typing .asst-bubble {
  background: transparent;
  border: 1px dashed var(--color-neutral-700);
  color: var(--color-neutral-500);
  font-style: italic;
}

.asst-foot {
  padding: var(--space-4) var(--space-6) var(--space-6);
  border-top: 1px solid var(--color-neutral-800);
  display: grid;
  gap: var(--space-4);
}
.asst-chip {
  cursor: pointer;
  font-family: var(--font-body);
  border-radius: var(--radius-md);
}
.asst-chip.tag-accent { border: 1px solid color-mix(in srgb, var(--color-accent) 45%, transparent); }
.asst-chip.tag-accent:hover { border-color: var(--color-accent); }
.asst-chip.tag-outline:hover { border-color: var(--color-accent); color: var(--color-accent-200); }

.asst-form {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-3);
}
.asst-form .input,
.asst-form .btn { font-family: var(--font-body); }
.asst-note {
  font-size: 11px;
  /* On a card, so a step lighter than the page-ground meta colour. */
  color: var(--color-neutral-500);
  margin: 0;
  line-height: 1.5;
}


/* ── Footer ──────────────────────────────────────────────────────────── */

.site-foot {
  margin-top: var(--section-gap);
  padding-top: calc(var(--space-8) * 2);
  padding-bottom: calc(var(--space-8) * 2.5);
  border-top: 1px solid var(--color-neutral-800);
}
.foot-grid {
  display: grid;
  grid-template-columns: minmax(min(280px, 100%), 1.6fr) repeat(auto-fit, minmax(min(150px, 100%), 1fr));
  gap: calc(var(--space-8) * 2) calc(var(--space-8) * 2.5);
}
.foot-head {
  font-family: var(--font-heading);
  font-weight: 500;
  font-size: clamp(24px, 3vw, 34px);
  letter-spacing: -0.03em;
  margin: 0 0 var(--space-6);
}
.foot-contact { display: grid; gap: var(--space-4); }
.foot-contact a,
.foot-contact span {
  font-size: 16px;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}
.foot-contact span { color: var(--color-neutral-500); }
.glyph { flex: none; opacity: 0.85; }

.foot-label {
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--color-neutral-600);
  margin-bottom: var(--space-4);
}
.foot-list {
  display: grid;
  gap: var(--space-2);
  font-size: 14px;
  color: var(--color-neutral-300);
}
.foot-bottom {
  margin-top: calc(var(--space-8) * 2);
  display: flex;
  justify-content: space-between;
  gap: var(--space-6);
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--color-neutral-600);
}


/* ── Narrow screens ──────────────────────────────────────────────────
   The timeline rail is the only layout that genuinely breaks on a phone:
   at 380px a 66px rail leaves too little for a card. Below 560px it goes
   above the entry instead of beside it. */

@media (max-width: 640px) {
  :root { --nav-h: 89px; }   /* section links on their own row */
  .nav-inner { flex-wrap: wrap; row-gap: var(--space-4); }
  .nav-links {
    order: 3;            /* below the wordmark and the CV button */
    flex: 1 0 100%;
    margin-left: 0;
    justify-content: space-between;
  }
  .btn-cv { margin-left: auto; }
  /* The extra nudge separates the email from the ghost button while they
     share a line. Once the row wraps it is just a stray indent. */
  .hero-email { margin-left: 0; }
}

@media (max-width: 360px) {
  :root { --nav-h: 123px; }  /* section links wrap to a second row */
}

@media (max-width: 560px) {
  .tl-row { grid-template-columns: 1fr; gap: var(--space-3); }
  .tl-rail {
    text-align: left;
    display: flex;
    align-items: baseline;
    gap: var(--space-4);
  }
  .tl-date { white-space: normal; }
  .tl-rail-label { margin-top: 0; }
  .tl-entry { padding-left: var(--space-6); }
  .tl-card { padding: var(--space-8); }
  .foot-bottom { gap: var(--space-2); }
  .foot-grid { grid-template-columns: 1fr; gap: calc(var(--space-8) * 1.5); }
  /* Title and date side by side leaves the wrapped title ragged against a
     floating date on a phone. Stack them, date first, which is how the
     expanded rows above already read at this width. */
  .tl-preview-row { grid-template-columns: 1fr; gap: var(--space-2); }
  .tl-preview-date { order: -1; }
}


/* ═══ 6. Motion ═════════════════════════════════════════════════════════
   Two keyframes only. Everything else on the page is a transition driven
   by an IntersectionObserver, so it degrades to "already visible" when
   scripting is off — see site.js. */

@keyframes fa-rise {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

@keyframes fa-blink {
  0%, 100% { opacity: 0.25; }
  50%      { opacity: 1; }
}


/* — scroll reveal —
   Opt-in, and the opt-in is the whole safety argument. site.js puts
   .js-reveal on <html> only once its observer exists and is already
   watching every element. Until then none of these rules apply, so a
   missing file, a script error, a browser without IntersectionObserver and
   scripting switched off all leave the page at its natural, visible state.
   Stranding content at opacity 0 is the worst failure available on a page
   whose only job is to be read, so it is made structurally impossible
   rather than merely avoided. */

.js-reveal [data-reveal]:not([data-stagger]) {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--reveal-dur) var(--reveal-ease),
              transform var(--reveal-dur) var(--reveal-ease);
}
.js-reveal [data-reveal]:not([data-stagger]).is-revealed {
  opacity: 1;
  transform: none;
}

/* A staggered group stays visible itself and animates its children in turn,
   sliding from the left rather than up. --stagger is copied off the
   element's data-stagger by site.js and --reveal-i is the child's index, so
   the delay is index x step and the numbers stay in the template. */
.js-reveal [data-stagger] > * {
  opacity: 0;
  transform: translateX(-26px);
  transition: opacity var(--reveal-dur) var(--reveal-ease),
              transform var(--reveal-dur) var(--reveal-ease);
  transition-delay: calc(var(--reveal-i, 0) * var(--stagger, 0ms));
}
.js-reveal [data-stagger].is-revealed > * {
  opacity: 1;
  transform: none;
}


/* — timeline dots and nav —
   Both are lit by observers in site.js. Neither hides anything: without the
   script the dots stay neutral and the nav links stay unhighlighted, which
   is a page missing an accent, not a page missing content. */
.tl-entry.is-lit .tl-dot {
  background: var(--color-accent);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-accent) 20%, transparent);
}
.nav-link.is-active { color: var(--color-accent-200); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  /* site.js does not add .js-reveal under reduced motion, so these should
     never be needed. They are here because "should never" is not a
     guarantee and this is the one thing that must not go wrong. */
  .js-reveal [data-reveal],
  .js-reveal [data-stagger] > * {
    opacity: 1 !important;
    transform: none !important;
  }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
