@charset "utf-8";

/* ==========================================================================
   martinpesout.cz
   One stylesheet, no build step, no framework, no preprocessor.

   The values here are distilled from the old WordPress theme (see
   design/tokens.md in the migration bundle) — the accent, the slab/sans
   pairing, the boxed-canvas feel. None of the old CSS was ported.

   Deliberate changes from the original:
   - Accent for TEXT is darkened. #F8862C on white is 2.6:1 and fails WCAG AA.
     #C25A00 is 4.9:1. The bright orange survives as a fill only.
   - Body type goes 14px -> fluid ~17px, measure capped at 68ch (was ~95ch).
   - Floats and 19 media queries -> grid + 2 breakpoints + container queries.
   - Dark mode, which the site never had.
   ========================================================================== */

@layer reset, tokens, base, layout, components, utilities;

/* ========================================================================== */
@layer reset {
  *, *::before, *::after { box-sizing: border-box; }

  * { margin: 0; }

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

  body { min-block-size: 100svb; -webkit-font-smoothing: antialiased; }

  img, picture, svg, video { display: block; max-inline-size: 100%; block-size: auto; }

  input, button, textarea, select { font: inherit; color: inherit; }

  p, h1, h2, h3, h4 { overflow-wrap: break-word; }

  :target { scroll-margin-block-start: 6rem; }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: 0.01ms !important;
      animation-iteration-count: 1 !important;
      transition-duration: 0.01ms !important;
      scroll-behavior: auto !important;
    }
  }
}

/* ========================================================================== */
@layer tokens {
  :root {
    color-scheme: light dark;

    /* ---- colour ---------------------------------------------------- */
    /* Two accents on purpose: the bright one is decorative, the dark one
       is the only one allowed to carry text. */
    --accent-fill:  #f8862c;
    --accent-text:  #c25a00;   /* 4.9:1 on white */
    --accent-hover: #9c4700;

    --ink:      light-dark(#242424, #f2f0ed);
    --body:     light-dark(#4a4a4a, #c3c0bb);
    --muted:    light-dark(#6f6f6f, #98948e);
    --hairline: light-dark(#e3e1de, #35322e);

    --surface:  light-dark(#ffffff, #16110c);
    --sunken:   light-dark(#faf8f5, #1e1914);
    --page:     light-dark(#f0eeea, #100c08);

    --footer-bg:   light-dark(#2b2724, #0b0806);
    --footer-ink:  #f0eeea;
    --footer-body: #b3aea7;

    --focus: light-dark(#0b57d0, #9dc0ff);

    /* ---- type ------------------------------------------------------- */
    --font-display: "Roboto Slab", ui-serif, Georgia, serif;
    --font-body: "Open Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
    --font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Consolas, monospace;

    --step--1: clamp(0.875rem, 0.85rem + 0.12vw, 0.94rem);
    --step-0:  clamp(1rem,     0.96rem + 0.2vw,  1.0625rem);
    --step-1:  clamp(1.2rem,   1.13rem + 0.35vw, 1.35rem);
    --step-2:  clamp(1.45rem,  1.32rem + 0.6vw,  1.75rem);
    --step-3:  clamp(1.75rem,  1.53rem + 1vw,    2.25rem);
    --step-4:  clamp(2.1rem,   1.72rem + 1.7vw,  3rem);

    --leading-tight: 1.15;
    --leading-body: 1.65;

    /* ---- space (4px base) -------------------------------------------- */
    --space-3xs: 0.25rem;
    --space-2xs: 0.5rem;
    --space-xs:  0.75rem;
    --space-sm:  1rem;
    --space-md:  1.5rem;
    --space-lg:  2rem;
    --space-xl:  3rem;
    --space-2xl: 4rem;
    --space-3xl: 6rem;

    /* ---- layout ------------------------------------------------------ */
    --wrap: 68rem;
    --wrap-narrow: 44rem;
    --measure: 68ch;
    --gutter: clamp(1rem, 4vw, 2.5rem);
    --radius: 6px;
    --radius-lg: 12px;
  }
}

/* ========================================================================== */
@layer base {
  /* In-page links (the hero's "Víc o mně ↓", the skip link) glide instead of
     jumping. Readers who ask for reduced motion keep the jump: the reset sets
     scroll-behavior: auto !important for them, and an important declaration in
     an earlier layer beats this one. :target's scroll-margin keeps the heading
     clear of the sticky header. */
  html { scroll-behavior: smooth; }

  body {
    background: var(--page);
    color: var(--body);
    font-family: var(--font-body);
    font-size: var(--step-0);
    line-height: var(--leading-body);
    display: flex;
    flex-direction: column;
  }

  h1, h2, h3, h4 {
    font-family: var(--font-display);
    font-weight: 400;
    line-height: var(--leading-tight);
    color: var(--ink);
    text-wrap: balance;
  }

  h1 { font-size: var(--step-4); }
  h2 { font-size: var(--step-3); }
  h3 { font-size: var(--step-2); }
  h4 { font-size: var(--step-1); }

  p { text-wrap: pretty; }

  a {
    color: var(--accent-text);
    text-decoration-thickness: 1px;
    text-underline-offset: 0.16em;
    transition: color 120ms ease;
  }
  a:hover { color: var(--accent-hover); }

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

  ::selection { background: var(--accent-fill); color: #fff; }

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

  hr { border: 0; border-block-start: 1px solid var(--hairline); margin-block: var(--space-xl); }
}

/* ========================================================================== */
@layer layout {
  .wrap {
    inline-size: min(100% - var(--gutter) * 2, var(--wrap));
    margin-inline: auto;
  }
  /* Capped at the measure as well: .prose narrows itself to --measure, and on a
     centred .wrap that re-centres the box, so a page body would sit ~20px right
     of its own h1 while the header and footer stayed put. Cap the column
     instead and every child of the article lines up. */
  .wrap--narrow { --wrap: min(var(--wrap-narrow), var(--measure)); }

  .site-main {
    flex: 1;
    background: var(--surface);
    padding-block: var(--space-2xl) var(--space-3xl);
  }

  .section { margin-block-start: var(--space-3xl); }
  .section:first-child { margin-block-start: 0; }

  .section__title {
    text-align: center;
    margin-block-end: var(--space-xl);
    position: relative;
  }
  /* The old theme broke a hairline with a star glyph. Same gesture,
     no icon font. */
  .section__title::after {
    content: "";
    display: block;
    inline-size: 3rem;
    block-size: 3px;
    background: var(--accent-fill);
    border-radius: 2px;
    margin: var(--space-md) auto 0;
  }

  .section__more { text-align: center; margin-block-start: var(--space-xl); }

  .cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
    gap: var(--space-lg);
  }

  .teasers { display: grid; gap: var(--space-xl); }
  .teasers--grid { grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr)); }
}

/* ========================================================================== */
@layer components {

  /* ---- skip link ----------------------------------------------------- */
  .skip-link {
    position: absolute;
    inset-block-start: var(--space-2xs);
    inset-inline-start: var(--space-2xs);
    padding: var(--space-2xs) var(--space-sm);
    background: var(--surface);
    border-radius: var(--radius);
    translate: 0 -200%;
    z-index: 10;
  }
  .skip-link:focus-visible { translate: 0; }

  /* ---- header -------------------------------------------------------- */
  .site-header {
    background: var(--surface);
    border-block-end: 1px solid var(--hairline);
    position: sticky;
    inset-block-start: 0;
    z-index: 5;
  }
  /* Frosted glass: content scrolling under the sticky header shows through,
     blurred, and on the homepage the hero's fog continues behind it. Only
     where backdrop-filter works: see-through text over unblurred content is
     hard to read, so other browsers keep the solid header above. 80% keeps
     the links readable when a bright image passes underneath. */
  @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .site-header {
      background: color-mix(in srgb, var(--surface) 80%, transparent);
      -webkit-backdrop-filter: blur(16px) saturate(1.2);
      backdrop-filter: blur(16px) saturate(1.2);
    }
  }
  .site-header__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm) var(--space-lg);
    padding-block: var(--space-sm);
  }

  .logo {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2xs);
    color: var(--ink);
    text-decoration: none;
    font-family: var(--font-display);
    font-weight: 600;
    font-size: var(--step-1);
  }
  .logo__mark { inline-size: 1.6em; block-size: 1.2em; color: var(--accent-fill); }
  .logo:hover .logo__text { color: var(--accent-hover); }

  .nav__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm) var(--space-md);
    list-style: none;
    padding: 0;
  }
  .nav__link {
    color: var(--body);
    text-decoration: none;
    font-weight: 600;
    font-size: var(--step--1);
    padding-block: var(--space-3xs);
    border-block-end: 2px solid transparent;
  }
  .nav__link:hover { color: var(--accent-hover); }
  .nav__link[aria-current="page"] {
    color: var(--accent-text);
    border-block-end-color: var(--accent-fill);
  }

  /* ---- hero ---------------------------------------------------------- */
  /* Phones and small tablets: text above the photo, centred as one unit.
     From 48rem the photo sits beside the text: that is where text (24rem),
     gap (4rem) and photo (16rem) first fit inside the gutters. */
  .hero {
    /* Narrower than the page's wide column: at 68rem the text column stretched
       far past where the text ends, leaving a band of empty space before the
       photo. At 56rem text and photo read as one unit. */
    --wrap: 56rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: var(--space-xl) var(--space-2xl);
    padding-block-end: var(--space-lg);
  }
  .hero .portrait { flex: none; }

  /* ---- portrait with rings ------------------------------------------- */
  /* Four rings sit behind the photo, 4px wider all round, so their lines
     clear its rim; the motion that sets them drifting is at the end of the
     file. Still, or with reduced motion, they lie on top of each other as
     one thin outline. Pencil lines in the ink colour, fading from ring to
     ring, and one in the accent. */
  .portrait { position: relative; isolation: isolate; }
  .portrait > .avatar { position: relative; z-index: 1; }
  .portrait__rings,
  .portrait__rings > span {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    pointer-events: none;
  }
  .portrait__rings {
    inset: -4px;                   /* a little larger than the photo, so the lines clear its rim */
    color: color-mix(in srgb, var(--ink) 55%, transparent);
  }
  .portrait__rings > span { border: 1.5px solid currentColor; }
  .portrait__rings > span:nth-child(2) { opacity: 0.7; }
  .portrait__rings > span:nth-child(3) { opacity: 0.5; }
  .portrait__rings > span:nth-child(4) { color: var(--accent-fill); }
  .hero__title { font-size: var(--step-4); }
  .hero__lead {
    font-size: var(--step-1);
    color: var(--muted);
    max-inline-size: 40ch;
    margin-block-start: var(--space-md);
    margin-inline: auto;
  }
  .hero__actions {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-sm);
    margin-block-start: var(--space-lg);
  }
  @media (min-width: 48rem) {
    .hero { flex-direction: row; text-align: start; }
    .hero__text { flex: 1 1 24rem; }
    .hero__lead { margin-inline: 0; }
    .hero__actions { justify-content: start; }
  }

  /* ---- feature card -------------------------------------------------- */
  .card {
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: var(--radius-lg);
    padding: var(--space-xl) var(--space-lg) var(--space-lg);
    margin-block-start: 2.2rem; /* room for the badge to hang out of the box */
    text-align: center;
    position: relative;
  }
  .card__icon {
    position: absolute;
    inset-block-start: 0;
    inset-inline-start: 50%;
    translate: -50% -50%;
    inline-size: 4.4rem;
    aspect-ratio: 1;
    display: grid;
    place-items: center;
    background: var(--accent-fill);
    color: #fff;
    border-radius: 50%;
  }
  .card__icon svg { inline-size: 1.9rem; }
  .card__title { font-size: var(--step-1); margin-block-end: var(--space-xs); }
  .card__text { color: var(--muted); font-size: var(--step--1); }
  .card__link { margin-block-start: var(--space-sm); }


  /* ---- post teaser --------------------------------------------------- */
  .teaser { display: grid; gap: var(--space-2xs); }
  .teaser__date {
    font-size: var(--step--1);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
  }
  .teaser__title { font-size: var(--step-2); }
  .teaser__title a { color: var(--ink); text-decoration: none; }
  .teaser__title a:hover { color: var(--accent-hover); text-decoration: underline; }
  .teaser__excerpt { color: var(--body); max-inline-size: var(--measure); }
  .teaser__cats { font-size: var(--step--1); color: var(--muted); }
  .teaser--compact .teaser__title { font-size: var(--step-1); }

  .teasers:not(.teasers--grid) .teaser {
    padding-block-end: var(--space-xl);
    border-block-end: 1px solid var(--hairline);
  }
  .teasers:not(.teasers--grid) .teaser:last-child { border-block-end: 0; padding-block-end: 0; }

  /* ---- post ---------------------------------------------------------- */
  .post__header { margin-block-end: var(--space-xl); }
  .post__title { margin-block-end: var(--space-sm); }
  .post__meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2xs);
    color: var(--muted);
    font-size: var(--step--1);
  }
  .post__footer { margin-block-start: var(--space-2xl); }

  .tags { display: flex; flex-wrap: wrap; gap: var(--space-2xs); margin-block-end: var(--space-lg); }
  .tag {
    font-size: var(--step--1);
    color: var(--muted);
    background: var(--sunken);
    border: 1px solid var(--hairline);
    border-radius: 999px;
    padding-inline: var(--space-xs);
  }

  .post__nav {
    display: grid;
    gap: var(--space-sm);
    grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
    padding-block: var(--space-lg);
    border-block: 1px solid var(--hairline);
  }
  .post__nav-link {
    text-decoration: none;
    color: var(--ink);
    font-family: var(--font-display);
  }
  .post__nav-link span {
    display: block;
    font-family: var(--font-body);
    font-size: var(--step--1);
    color: var(--muted);
  }
  .post__nav-link:hover { color: var(--accent-hover); }
  .post__nav-link--prev { text-align: end; }
  .post__back { margin-block-start: var(--space-lg); }

  /* ---- prose --------------------------------------------------------- */
  /* Styling for article text: the Markdown output of posts and pages.
     Apart from the container itself, every rule here is written
     :where(.prose) …, and :where() counts for nothing in specificity — so a
     prose default weighs no more than a bare element selector, and any
     component class wins wherever it sits, in any source order. No :not()
     exclusions, no .prose-scoped overrides. (Before, `.prose img` and friends
     at 0,1,1 beat a component's 0,1,0: they squared off the avatar, pushed the
     CTA heading 48px down, and staggered the social icons.)

     The flip side: a component placed in prose must declare the properties it
     owns — the avatar its border and margin, the social list its item
     margins, the CTA heading its top margin. Prose fills in only what a
     component leaves unsaid. */
  .prose { max-inline-size: var(--measure); container-type: inline-size; }
  :where(.prose) > * + * { margin-block-start: var(--space-md); }
  :where(.prose) h2 { margin-block-start: var(--space-2xl); font-size: var(--step-2); }
  :where(.prose) h3 { margin-block-start: var(--space-xl); font-size: var(--step-1); }
  :where(.prose) :is(ul, ol) { padding-inline-start: var(--space-md); }
  :where(.prose) li + li { margin-block-start: var(--space-2xs); }
  /* photos in the body copy */
  :where(.prose) img {
    border-radius: var(--radius);
    border: 1px solid var(--hairline);
    margin-inline: auto;
  }
  :where(.prose) code:not(pre code) {
    background: var(--sunken);
    border: 1px solid var(--hairline);
    border-radius: 4px;
    padding: 0.1em 0.35em;
    color: var(--ink);
  }
  :where(.prose) pre {
    background: var(--sunken);
    border: 1px solid var(--hairline);
    border-radius: var(--radius);
    padding: var(--space-sm) var(--space-md);
    overflow-x: auto;
    line-height: 1.55;
  }
  :where(.prose) blockquote {
    border-inline-start: 3px solid var(--accent-fill);
    padding-inline-start: var(--space-md);
    color: var(--ink);
    font-style: italic;
  }
  :where(.prose) blockquote p + p { margin-block-start: var(--space-sm); }

  /* Full-bleed escape for wide code and images inside the narrow measure */
  :where(.prose) :where(pre, .embed) { max-inline-size: min(100%, 52rem); }

  /* ---- billing table (/kontakt/) ------------------------------------- */
  /* A two-column label/value table whose cells carry the browser's 1px
     default, so the label ran into its value. Scoped to the page rather than
     to .prose: a table in a future post should keep the browser's own layout
     instead of inheriting a shape chosen for this one. */
  .page--contact :is(th, td):first-child {
    padding-inline-end: var(--space-md);
    white-space: nowrap;
  }

  /* ---- CTA (exported from the old .w-actionbox) ----------------------- */
  /* Text beside the button when the box has room, button below when it has
     not — decided by the box's own width, so it lays out the same wherever it
     is placed. It used a container query, which silently stopped matching once
     the box sat outside .prose (the only container around it back then). */
  .cta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-md);
    background: var(--sunken);
    border: 1px solid var(--hairline);
    border-inline-start: 4px solid var(--accent-fill);
    border-radius: var(--radius);
    padding: var(--space-lg);
    margin-block-start: var(--space-2xl);
  }
  /* A 12rem floor, not more: in the narrow column (~661px, capped by the line
     length) photo + text + an e-mail button then share one row with room to
     spare. The text still takes all leftover width. */
  .cta__body { flex: 1 1 12rem; }
  .cta > .avatar { flex: none; }
  .cta__title { font-size: var(--step-1); margin-block-start: 0; }
  /* Body size, not a step below: the button beside it uses body size too, and
     a description smaller than its own button label reads upside down. */
  .cta__text { color: var(--muted); font-size: var(--step-0); margin-block-start: var(--space-2xs); }
  .cta__action { margin: 0; flex: none; }

  /* ---- avatar -------------------------------------------------------- */
  .avatar {
    inline-size: var(--avatar-size);
    aspect-ratio: 1;
    border-radius: 50%;
    object-fit: cover;
    /* declared, so the prose photo frame and centring never reach it */
    border: 0;
    margin-inline: 0;
  }
  .avatar--sm { --avatar-size: 4rem; }
  .avatar--md { --avatar-size: 6rem; }
  .avatar--lg { --avatar-size: 10rem; }
  .avatar--xl { --avatar-size: 16rem; }

  .button-row { margin-block: var(--space-lg); }
  .embed--codepen { font-size: var(--step--1); }

  /* ---- button -------------------------------------------------------- */
  .button {
    display: inline-block;
    background: var(--accent-text);
    color: #fff;
    text-decoration: none;
    font-weight: 600;
    font-size: var(--step-0);
    /* 10px, between the 8px and 12px steps: ~50px tall reads less boxy than
       54px beside body text, and stays well over the 44px touch minimum. */
    padding: 0.625rem var(--space-lg);
    border-radius: var(--radius);
    border: 1px solid transparent;
    transition: background-color 120ms ease;
  }
  .button:hover { background: var(--accent-hover); color: #fff; }
  .button--ghost {
    background: transparent;
    color: var(--accent-text);
    border-color: var(--hairline);
  }
  .button--ghost:hover { background: var(--sunken); color: var(--accent-hover); }

  /* ---- page ---------------------------------------------------------- */
  .page__header { margin-block-end: var(--space-xl); }
  .page__lead { color: var(--muted); margin-block-start: var(--space-2xs); }
  .page__back { margin-block-start: var(--space-2xl); }
  .eyebrow { font-size: var(--step--1); color: var(--muted); }

  /* ---- pagination ---------------------------------------------------- */
  .pagination {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-sm);
    margin-block-start: var(--space-2xl);
    padding-block-start: var(--space-lg);
    border-block-start: 1px solid var(--hairline);
    font-size: var(--step--1);
  }
  .pagination__status { color: var(--muted); }

  /* ---- footer -------------------------------------------------------- */
  .site-footer { background: var(--footer-bg); color: var(--footer-body); }
  .site-footer a { color: var(--footer-ink); }
  .site-footer a:hover { color: var(--accent-fill); }
  .site-footer__inner {
    display: grid;
    gap: var(--space-xl);
    grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
    padding-block: var(--space-2xl);
  }
  .site-footer__heading {
    color: var(--footer-ink);
    font-size: var(--step-1);
    margin-block-end: var(--space-sm);
  }
  .site-footer p + p { margin-block-start: var(--space-sm); }
  /* ---- social links (patička i /kontakt/) ----------------------------- */
  .social {
    --social-icon: 1.5rem;
    --social-hit: 2.75rem;

    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2xs);
    list-style: none;
    padding: 0;
    margin-block-start: var(--space-sm);
    /* Optické zarovnání: dotykový cíl je větší než ikona, takže by první
       značka začínala o půl rozdílu vpravo od textu nad sebou. Řádek se o
       tenhle půlrozdíl posune zpět — dopočítaný, ne opsaný. */
    margin-inline-start: calc((var(--social-hit) - var(--social-icon)) / -2);
  }
  .social__link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    /* Ikona sama měří 1.5rem, což je na prst málo. Viz
       /velikosti-dotykovych-oblasti-pod-drobnohledem/. */
    min-inline-size: var(--social-hit);
    min-block-size: var(--social-hit);
  }
  .social__link svg { inline-size: var(--social-icon); block-size: var(--social-icon); }
  /* the list's own item spacing: the gap above, never prose's li + li margin */
  .social > li { margin: 0; }
  .site-footer__legal {
    border-block-start: 1px solid #ffffff1f;
    padding-block: var(--space-md);
    font-size: var(--step--1);
    color: var(--footer-body);
  }

  /* ---- about sections (homepage, which absorbed /o-mne/) --------------- */
  .about__tools { list-style: none; padding: 0; margin-block-end: 0; }
  .about__section { margin-block-start: var(--space-3xl); }
  .about__section--first { margin-block-start: var(--space-2xl); }
  .about__section > h2 { margin-block-end: var(--space-lg); }

  .stats {
    list-style: none;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
    gap: var(--space-md);
    margin-block-start: var(--space-xl);
  }
  .stats__item {
    display: grid;
    align-content: start;
    gap: var(--space-3xs);
    padding-block-start: var(--space-sm);
    border-block-start: 3px solid var(--accent-fill);
  }
  .stats__value {
    font-family: var(--font-display);
    font-size: var(--step-3);
    font-weight: 400;
    line-height: var(--leading-tight);
    color: var(--ink);
  }
  .stats__label { color: var(--muted); font-size: var(--step--1); }

  .experience {
    container-type: inline-size;
    list-style: none;
    padding: 0;
    display: grid;
    gap: var(--space-lg);
  }
  .experience__item { display: grid; gap: var(--space-3xs) var(--space-lg); }
  @container (min-width: 34rem) {
    .experience__item { grid-template-columns: 8rem 1fr; }
  }
  .experience__years {
    color: var(--accent-text);
    font-size: var(--step--1);
    font-variant-numeric: tabular-nums;
    padding-block-start: 0.25em;
  }
  .experience__role { font-size: var(--step-1); }
  .experience__company { color: var(--ink); font-size: var(--step--1); font-weight: 600; margin-block-start: var(--space-3xs); }
  .experience__text { color: var(--muted); margin-block-start: var(--space-2xs); }
  .experience__education { margin-block-start: var(--space-xl); color: var(--muted); font-size: var(--step--1); }
}

/* ========================================================================== */
@layer utilities {
  .visually-hidden:not(:focus):not(:active) {
    clip-path: inset(50%);
    block-size: 1px;
    inline-size: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
  }
  .text-center { text-align: center; }
}

/* ==========================================================================
   Cross-document view transitions. Progressive: browsers without support
   just navigate normally.
   ========================================================================== */
@view-transition { navigation: auto; }

/* ==========================================================================
   Scroll-driven effects and the homepage hero background
   The scroll-driven parts run only where animation-timeline is supported
   (Chrome/Edge 115+, Safari 26+); Firefox shows the static page.
   ========================================================================== */

/* ---- reading progress (blog posts) --------------------------------------
   Markup: the .reading-progress div in components/header.njk (posts only).
   Not gated by reduced motion: it moves only as the reader scrolls and
   reports where they are, rather than decorating. */
@layer components {
  .reading-progress { display: none; }
  @supports (animation-timeline: scroll()) {
    .reading-progress {
      display: block;
      position: absolute;
      inset-inline: 0;
      inset-block-end: -1px;          /* over the header's bottom border */
      block-size: 3px;
      background: var(--accent-fill);
      transform-origin: 0 50%;
      scale: 0 1;
      animation: reading-progress linear both;
      animation-timeline: scroll(root);
    }
  }
  @keyframes reading-progress { to { scale: 1 1; } }
}

/* ---- technology tags sliding in two rows (homepage) ----------------------
   Markup: the .tag-rows wrapper and its second, aria-hidden row in
   src/index.njk. Without support, or with reduced motion, the first row wraps
   as a normal list and the decorative second row stays hidden. */
@layer components {
  .tag-rows__row--reverse { display: none; }
  @media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) {
      .tag-rows {
        display: grid;
        gap: var(--space-2xs);
        overflow: clip;
        view-timeline: --tag-rows;
        /* soft edges instead of a hard cut where the rows leave the column */
        mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
      }
      .tag-rows__row {
        flex-wrap: nowrap;
        inline-size: max-content;
        animation: tag-row-slide linear both;
        animation-timeline: --tag-rows;
        animation-range: cover 0% cover 100%;
      }
      .tag-rows__row--reverse {
        display: flex;
        animation-name: tag-row-slide-back;
      }
    }
  }
  /* 12% of the row's width over the whole pass: slow enough to read along */
  @keyframes tag-row-slide      { from { translate: 0 0; }    to { translate: -12% 0; } }
  @keyframes tag-row-slide-back { from { translate: -12% 0; } to { translate: 0 0; } }
}

/* ---- glint across the hero heading (homepage) ---------------------------
   One warm sheen passes over the heading once, 1.6s after a short pause. The
   text is painted by its background (background-clip: text), so the h1 stays
   plain text for screen readers and is fully visible from the first frame:
   both ends of the sweep are the ordinary ink colour. In the light theme the
   sheen is the darker text accent, so the letters it crosses stay readable.
   Not with reduced motion, and not in forced colours, where a transparent
   text colour would leave nothing to see. */
@layer components {
  @media (prefers-reduced-motion: no-preference) and (forced-colors: none) {
    @supports (background-clip: text) or (-webkit-background-clip: text) {
      .hero__title {
        --glint-edge: light-dark(var(--accent-text), var(--accent-fill));
        --glint-peak: light-dark(#a34b00, #ffc38a);
        background:
          linear-gradient(100deg,
            var(--ink) 40%, var(--glint-edge) 47%, var(--glint-peak) 50%,
            var(--glint-edge) 53%, var(--ink) 60%)
          100% 0 / 250% 100% no-repeat;
        -webkit-background-clip: text;
        background-clip: text;
        color: transparent;
        animation: hero-glint 1.6s 0.4s cubic-bezier(0.4, 0, 0.2, 1) both;
      }
    }
  }
  @keyframes hero-glint { to { background-position: 0 0; } }
}

/* ---- rings around the portrait (homepage) -------------------------------
   Markup: the .portrait wrapper in src/index.njk; colours next to the hero
   rules. Each ring drifts, turns and goes slightly oval on its own period
   (7.3/9.7/11.9/13.1s), so the whole never quite repeats, like pencil lines
   retracing a contour. It runs endlessly by choice: a deliberate exception
   to WCAG 2.2.2's pause control, decided on 2026-09-28. Reduced motion
   still switches it off, and the rings then rest as one outline. */
@layer components {
  @media (prefers-reduced-motion: no-preference) {
    .portrait__rings > span:nth-child(1) { animation: ring-drift-1 7.3s ease-in-out infinite alternate; }
    .portrait__rings > span:nth-child(2) { animation: ring-drift-2 9.7s ease-in-out infinite alternate; }
    .portrait__rings > span:nth-child(3) { animation: ring-drift-3 11.9s ease-in-out infinite alternate; }
    .portrait__rings > span:nth-child(4) { animation: ring-drift-4 13.1s ease-in-out infinite alternate; }
  }

  @keyframes ring-drift-1 {
    0%   { transform: translate(-5px, 3px) rotate(0deg) scale(1.02, 0.99); }
    33%  { transform: translate(4px, -6px) rotate(40deg) scale(0.99, 1.03); }
    66%  { transform: translate(7px, 4px) rotate(95deg) scale(1.03, 1); }
    100% { transform: translate(-3px, -4px) rotate(150deg) scale(1, 1.02); }
  }
  @keyframes ring-drift-2 {
    0%   { transform: translate(6px, -2px) rotate(20deg) scale(0.98, 1.03); }
    40%  { transform: translate(-7px, 5px) rotate(-30deg) scale(1.03, 0.99); }
    100% { transform: translate(3px, 8px) rotate(-80deg) scale(1.01, 1.02); }
  }
  @keyframes ring-drift-3 {
    0%   { transform: translate(0, -8px) rotate(-10deg) scale(1.03, 1); }
    50%  { transform: translate(-8px, -1px) rotate(60deg) scale(1, 1.04); }
    100% { transform: translate(5px, 6px) rotate(120deg) scale(1.02, 0.98); }
  }
  @keyframes ring-drift-4 {
    0%   { transform: translate(8px, 6px) rotate(0deg) scale(1.01, 0.98); }
    50%  { transform: translate(-4px, -8px) rotate(-70deg) scale(0.98, 1.02); }
    100% { transform: translate(-9px, 3px) rotate(-130deg) scale(1.02, 1.01); }
  }
}

/* ---- hero fog (homepage) ---------------------------------------------------
   Static — nothing moves. A few soft, irregular clouds of colour instead of
   one even gradient: an amber glow behind the photo (the accent, as a halo),
   a plum haze from the top left (orange + plum reads as dusk), and a faint
   teal low on the left, the cool complement that keeps it from going flat.
   Every colour is kept soft and set per theme with light-dark(), checked
   against the contrast of the hero text; the whole layer fades out towards
   the bottom of the hero, so it has no edge. It starts at the very top of the
   page, under the header, and shows through it because the header is frosted
   glass (see "header" above). */
@layer components {
  .hero:has(.hero__text) {
    position: relative;
    isolation: isolate;
    --fog-amber: light-dark(rgb(248 134 44 / 0.34), rgb(248 134 44 / 0.30));
    --fog-plum:  light-dark(rgb(168 72 128 / 0.20), rgb(150 58 118 / 0.34));
    --fog-teal:  light-dark(rgb(38 128 130 / 0.16), rgb(36 118 124 / 0.22));
    --fog-amber-at: 70% 60%;   /* behind the photo, which sits right of the text */
    --fog-amber-size: 38% 62%;
    --fog-plum-size: 50% 68%;
  }
  .hero:has(.hero__text)::before {
    content: "";
    position: absolute;
    z-index: -1;
    /* from the top of the page — under the header, whose height varies as
       the navigation wraps, so lift generously — to the end of the hero,
       across the whole window */
    inset-block: calc((var(--space-2xl) + 9rem) * -1) calc(var(--space-lg) * -1);
    inset-inline: calc(50% - 50vw);
    pointer-events: none;
    background:
      radial-gradient(ellipse var(--fog-amber-size) at var(--fog-amber-at), var(--fog-amber), transparent 72%),
      radial-gradient(ellipse var(--fog-plum-size) at 16% 22%, var(--fog-plum),  transparent 70%),
      radial-gradient(ellipse 34% 40% at 6% 92%,  var(--fog-teal),  transparent 72%),
      radial-gradient(ellipse 64% 40% at 50% 8%,  var(--fog-plum), transparent 75%),
      /* base: the content colour, so the strip under the header matches the
         hero below it (the page ground there is a different shade) */
      linear-gradient(var(--surface), var(--surface));
    mask-image: linear-gradient(to bottom, #000 70%, transparent);
  }
  /* Phones: the hero is tall and narrow, and sizes in % of it stretch
     vertically — the plum reached down behind the lead text and the amber up
     behind the button, dropping their contrast (lead 4.32:1, button 3.70:1).
     So the clouds get wide, short shapes: plum stays at the top, the amber
     sits on the photo, which is centred below the text. */
  @media (max-width: 40rem) {
    .hero:has(.hero__text) {
      --fog-amber-at: 50% 88%;
      --fog-amber-size: 90% 22%;
      --fog-plum-size: 110% 24%;
    }
  }
  /* the layer is wider than the column: never let it add a scrollbar */
  .site-main:has(.hero__text) { overflow-x: clip; }

}
