/* ============================================================================
   PREVIEW LAYOUT ADDITIONS  —  loaded only by _layouts/letterhead.html,
   only on /preview/, and only AFTER my_css.css.

   This file adds ARRANGEMENT, borrowed from lpwpower.github.io: where blocks
   sit, how they pair, where the dividing rules run. It adds no colour, no
   font, and no new hue. Every value below resolves through my_css.css's own
   tokens (--hairline, --hairline-strong, --border-mid, --text-*), so dark
   mode follows for free and nothing needs a second declaration.

   Everything is scoped under `body.lh`, which only this layout sets. The live
   index.html cannot be reached by any rule here.

   NOT TOUCHED, deliberately: the publication rows. .pub, .pub-year,
   .pub-title, .pub-authors, .pub-meta, .pub-abstract, .category-toggle and
   every badge keep their my_css.css values exactly. The single rule below
   that reaches #publications is the head rule on .section-header-row, which
   is the section heading, not the list.
   ============================================================================ */


/* ================ 1. Header: banner + letterhead row ================
   The banner itself is untouched -- same source, same clamp(160px,26vw,300px)
   height, same object-fit. Only its bottom corners are squared, so the row
   welded underneath reads as one object with it rather than two stacked cards. */
.lh .header {
  text-align: left;
  /* Tight to the navbar. The navbar centres its own links on a 5.2rem line box,
     which already puts ~26px of air above the link text, so anything generous
     here reads as a second blank line stacked on top of that one. */
  margin-bottom: 4px;
}

/* No frame. The banner keeps its full 12px radius and the identity line sits
   under it in open space.

   The zero side padding is the point: with no box to sit inside, the name has
   to hang on the same left edge as every section heading below it, or the
   inset reads as a misalignment rather than as padding. Container edge for the
   banner, the name, and the section headings alike. */
.lh .banner-img {
  border-radius: 12px;
}

.lh .letterhead {
  padding: 20px 0 0;
}

/* Name, address, links: three lines, each directly under the last. Set flush
   left rather than pushed to the right edge -- once the letterhead border was
   dropped there was nothing tying a right-aligned icon row to the name, and it
   simply floated in the gap. */
/* Optical alignment. Boxes all start at the container edge, but ink does not:
   a glyph sits inside its own left side bearing, and that bearing scales with
   type size. Measured at 4x against the banner image, whose left edge is a hard
   140.00 and so is the page's true left edge:

     banner / icon glyphs   140.00   (no bearing)
     email "e"              140.50   (+0.50)
     name  "E"              144.25   (+4.25)  <- the one you can see

   text-indent rather than a negative margin: it moves the glyph and leaves the
   box, so nothing about the layout shifts. Both values are the measured bearing
   negated, which lands all three ink edges on 140.00 flush with the banner.
   The email's 0.50 is sub-pixel and invisible at 1x -- it is corrected only
   because the rule was being written anyway, and it does show on a retina
   display. "Eric Chen" cannot wrap at any supported width, so first-line-only
   indent is safe. */
.lh .letterhead h1 {
  margin: 0 0 5px;
  line-height: 1.1;
  text-indent: -4.25px;
}

.lh .lh-contact {
  color: var(--text-soft);
  margin: 0 0 9px;
  line-height: 1.4;
  text-indent: -0.5px;
}

.lh .letterhead .social-icons {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0;
  line-height: 1;
  white-space: nowrap;
}

.lh .letterhead .social-icons > span {
  display: inline-flex;
  align-items: center;
  line-height: 1;
}

/* ================ 2. The divider moves under the heading ================
   my_css.css puts a border-top on .docs-section, so every rule sits ABOVE a
   section with its heading floating free below it. Her page runs the rule
   UNDER the head instead, which is what makes a page read as indexed rather
   than as a stack. Same hairline, same 1px, moved. */
.lh .docs-section {
  border-top: 0;
  padding: 2.6rem 0 0;
  /* Anchor targets have to clear the docked navbar, which is fixed over the top
     5.2rem of the viewport -- without this the browser scrolls the section to
     y=0 and the bar sits on top of the heading. 5.2rem is the bar's own height,
     so the section's top edge lands exactly at its lower edge and the section's
     2.6rem of top padding becomes the air between them. */
  scroll-margin-top: 5.2rem;
}

/* Skeleton gives h4 a bottom margin; the rule needs its own breathing room, so
   the margin is restated here rather than inherited. */
.lh .docs-section > h4,
.lh .section-header-row {
  border-bottom: 1px solid var(--hairline);
  padding-bottom: 10px;
  margin-bottom: 22px;
}

/* #publications heads with a row (h4 + area filter), so the rule belongs on
   the row, not the h4 -- otherwise it would stop short of the filter. The h4
   inside already has margin-bottom: 0 from my_css.css:302. */
.lh .section-header-row > h4 {
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: 0;
}

/* #miscellaneous nests its own hairlines between subsections; its <h4> takes
   the head rule like any other, and the first subsection's own border-top is
   already suppressed by its inline :first-of-type rule. */


/* ================ Links ================
   Deeper and more oceanic, as asked -- darker, not brighter.

     light  #4772b5 -> #2971b3   OKLCH L 0.5521 -> 0.536 - C 0.115 -> 0.125 - H 259 -> 250
     dark   #6594dd -> #4d95db   OKLCH L 0.6645 -> 0.655 - C 0.120 -> 0.128 - H 259 -> 250

   The lightness step is deliberately small: L* 47.9 -> 46.4 is -1.5, right at
   the threshold you can see, which is what "a tad" buys before the 3:1 floor
   against body ink starts binding. Because it goes DOWN, contrast on white
   goes UP -- 4.84:1 -> 5.11:1 -- so it reads deeper rather than louder, while
   3.11:1 vs #222 still clears the floor. Dark keeps its own lightness band
   (5.72:1 on #14161a) since darkening there would cost contrast, not add it.

   Hue moves 9 degrees toward cyan, which is the oceanic direction, and both
   themes stay locked to one hue. Chroma is 88% of the slides badge (C 0.1423),
   so :405 still holds. Only the token is redefined; its consumers are
   unchanged and none of them is inside #publications. */
.lh {
  --link: #2971b3;
}

:root[data-theme="dark"] .lh {
  --link: #4d95db;
}

/* The site always stamps data-theme before first paint, so this is a no-op
   duplicate there; it is what carries the dark value in contexts that leave the
   root unstamped and fall back to the OS preference. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .lh {
    --link: #4d95db;
  }
}

/* my_css.css:98 lists the prose links by section id -- #intro p a, #research p
   a, .news-text a and so on. Renaming the intro to .about-prose dropped these
   links off that list, so they were rendering as body ink with a solid
   underline. Same declarations, verbatim, for the new selector. */
.lh .about-prose a {
  color: var(--link);
  text-decoration: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
  transition: text-decoration-color 0.2s ease;
}

.lh .about-prose a:hover {
  text-decoration-color: currentColor;
}


/* ================ 3. The duo ================
   Her .duo, at her ratio: the bio on the left, the rolling figure on the right.
   It lives INSIDE #about, under that section's single heading rule, so it
   carries no rules of its own and the page keeps one rule per section all the
   way down. Collapses at 749px -- the same breakpoint .research-areas already
   uses, so the page has one tablet threshold. */
.lh .duo {
  display: grid;
  grid-template-columns: 1.15fr .85fr;
  gap: 0 48px;
  align-items: start;
}

/* :last-child, not p:last-child -- the bio now ends in a <ul>, so the old
   paragraph-only selector stopped matching and left a trailing margin. */
.lh .about-prose > :last-child {
  margin-bottom: 0;
}

/* The goals list. Skeleton ships `list-style: circle inside`, and `inside` is
   what made it look wrong: the marker joins the first line box, so wrapped
   lines return to the marker's own left edge instead of hanging under the text.
   Every line after the first sat under the bullet.

   Marker is a 11px hairline rather than a disc -- the page is built out of
   1px rules, so a short rule reads as part of that vocabulary where a filled
   dot would be the one blob on the page. Absolutely placed against a padded
   hanging indent, so every wrapped line clears it. */
/* One rhythm through the whole bio. Skeleton gives paragraphs a 25px bottom
   margin, which left three different gaps stacked in one block: 25px between
   the paragraphs, 12px from the lead-in to the list, 14px between the items.
   All three are 14px now, so the block reads as a single unit rather than as
   three loosely related ones.

   Equalising them also retires the selector that used to single out the
   lead-in paragraph -- when every gap is the same value there is nothing to
   single out, and the spacing stops depending on a selector some browsers do
   not support. */
.lh .about-prose p {
  margin-bottom: 14px;
}

.lh .about-goals {
  list-style: none;
  margin: 0;
  padding: 0;
}

.lh .about-goals li {
  position: relative;
  padding-left: 24px;
  margin-bottom: 14px;
  line-height: 1.6;
}

.lh .about-goals li:last-child {
  margin-bottom: 0;
}

.lh .about-goals li::before {
  content: "";
  position: absolute;
  left: 0;
  /* .8em is the optical middle of a 1.6 line box, so the rule sits on the
     first line's centre rather than its top. */
  top: .8em;
  width: 11px;
  height: 1px;
  background: var(--border-mid);
}

/* The term is the thing being scanned; it takes the strong ink and the same
   600 the section headings and .pub-title use. */
.lh .about-goals strong {
  font-weight: 600;
  color: var(--text-strong);
}


/* The news log's 2px rule is segmented by .news-list's 12px gap, so it reads as
   a stack of floating ticks rather than as one margin rule. Spacing moves into
   each row's padding instead, which is exactly how .pub does it (my_css.css:341
   -- "spacing lives in each row's padding, not in a list gap, so adjacent
   borders meet"). Same idiom, now used in both lists. */
.lh #news .news-list {
  gap: 0;
}

.lh #news .news-item {
  padding-bottom: 12px;
}

.lh #news .news-item:last-child {
  padding-bottom: 0;
}


/* ================ Rolling research areas ================
   The three area cards, shown one at a time instead of side by side.

   Almost nothing here is new paint: the image, the name and the keyword line
   are .area-image / .area-name / .area-keywords straight from my_css.css, so
   the 8px radius, the 12px caption gap, the dark-mode dimming and the
   middle-dot separator all come from the rules already tuned for the grid.
   What is added is the stacking, the crossfade and the controls. */
.lh .ar-stage,
.lh .ar-captions {
  display: grid;              /* one cell, every slide in it: the box is as tall
                                 as the tallest of the three, so advancing never
                                 shifts the page */
  position: relative;
}

.lh .ar-slide,
.lh .ar-cap {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;         /* not display:none -- the cell must keep sizing to
                                 every slide, and this drops the hidden ones out
                                 of the tab order too */
  transition: opacity 0.55s ease, visibility 0.55s;
}

.lh .ar-slide.is-active,
.lh .ar-cap.is-active {
  opacity: 1;
  visibility: visible;
}

/* --- arrows: bare marks over the figure, no chrome --- */
.lh .ar-arrow {
  position: absolute;
  /* .area-image carries a 12px bottom margin inside the cell, so dead-centre
     of the cell sits 6px below the centre of the picture. */
  top: calc(50% - 6px);
  transform: translateY(-50%);
  /* The hit area is larger than the mark it holds -- 40x34 around a 28x24
     glyph -- so the target stays comfortable without drawing anything. */
  width: 40px;
  height: 34px;
  padding: 0;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Skeleton styles every <button> (38px tall, uppercase, tracked, bordered);
     undo all of it. font-size:0 also kills the stray text line box, which would
     otherwise tug the flex centring. */
  line-height: 1;
  font-size: 0;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--text-soft);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.25s ease, color 0.2s ease;
}

.lh .ar-prev { left: 2px; }
.lh .ar-next { right: 2px; }

/* Legibility without a shape. Two soft shadows in the PAGE colour halo the
   stroke, so a 1.5px hairline holds up over any figure -- and because the
   halo is --bg it inverts with the theme on its own. This is what replaces the
   old bordered circle: the mark stays readable, nothing is drawn around it. */
.lh .ar-arrow svg {
  display: block;
  filter: drop-shadow(0 0 2px var(--bg)) drop-shadow(0 0 5px var(--bg));
}

/* The head is always there; the tail draws out of its vertex on hover, so the
   mark resolves from a chevron into a full arrow under the cursor. Dash length
   equals the path length, so offsetting by it hides the tail completely and
   easing to 0 draws it from the vertex outward -- which is why the prev tail is
   authored left-to-right and the next tail right-to-left. */
.lh .ar-tail {
  stroke-dasharray: 15;
  stroke-dashoffset: 15;
  transition: stroke-dashoffset 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}

.lh .ar-arrow:hover .ar-tail,
.lh .ar-arrow:focus-visible .ar-tail {
  stroke-dashoffset: 0;
}

/* Revealed by hovering the figure, not each arrow -- otherwise you would have
   to find an invisible target before it appeared. Dropping this rule is what
   made them vanish entirely, so it stays adjacent to the opacity:0 above. */
.lh .area-rotator:hover .ar-arrow {
  opacity: 1;
}

.lh .ar-arrow:hover {
  color: var(--text-strong);
}

/* my_css.css:136 rings links only, and these are buttons. Same idiom: ink, not
   accent, so nothing new lands near the publications section. */
.lh .ar-arrow:focus-visible {
  opacity: 1;
  outline: 2px solid var(--text-strong);
  outline-offset: 2px;
  border-radius: 3px;
}

/* One area: nothing to roll between. */
.lh .area-rotator.ar-single .ar-arrow {
  display: none;
}

/* The script also stops auto-advancing here, leaving the arrows as the only way
   through -- with no hover pause left, they are the pause. */
@media (prefers-reduced-motion: reduce) {
  .lh .ar-slide,
  .lh .ar-cap,
  .lh .ar-arrow { transition: none; }
  /* The tail is shown outright rather than drawn. */
  .lh .ar-tail { transition: none; stroke-dashoffset: 0; }
  .lh .ar-arrow { opacity: 1; }
}


/* Stack at 899px, not 749. The figure lives in the .85fr track, and between
   750 and 900 that track collapses to 238-286px -- a postage stamp beside a
   500px column of text. Below 900 there is not room for both a readable
   measure and a figure worth showing, so they stack and the figure goes full
   width. At 900 exactly the figure is still 286px; above that it only grows. */
@media (max-width: 899px) {
  .lh .duo {
    grid-template-columns: 1fr;
  }
  /* Stacked, the figure drops under the prose and needs the gap the 48px
     column gutter was providing. */
  .lh .duo > .area-rotator {
    margin-top: 28px;
  }
}

/* Touch, not narrow: the arrows are revealed on hover, and a device without
   hover would never show them at all. Keyed to the capability rather than to a
   width, so a wide tablet is covered and a narrow desktop window is not. */
@media (hover: none) {
  .lh .ar-arrow {
    opacity: 1;
  }
}


/* ================ 4. Navbar: wordmark + scroll gauge ================
   The navbar itself -- docking, backdrop blur, the sibling fade-out on hover,
   the scaleX underline -- is entirely my_css.css and untouched. This turns its
   container into a flex row and hangs two things off it. Both are desktop-only
   for free: .navbar is display:none below 750px. */
@media (min-width: 750px) {
  .lh .navbar > .container {
    display: flex;
    align-items: center;
    gap: 32px;
  }

  /* Same type as .navbar-link, one step darker, so it reads as the sheet's
     label rather than as a seventh nav item.

     Hidden until the navbar pins. Undocked, the letterhead's <h1> sits directly
     above it and the name would be printed twice on one screen; once the bar
     docks, that <h1> has scrolled away and the wordmark takes over as the only
     place the name appears. The two are never visible together -- the
     letterhead's last pixel is above the fold by the time the bar pins.

     visibility rather than display, so the flex row keeps its geometry and the
     nav links never shift; it also takes the link out of the tab order while it
     is invisible. Fades in alongside the gauge, on the same 0.3s. */
  .lh .wordmark {
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0.3s;
    text-transform: uppercase;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: .2rem;
    line-height: 5.2rem;
    color: var(--text-strong);
    text-decoration: none;
    white-space: nowrap;
    flex: none;
  }

  .lh.has-docked-nav .wordmark {
    opacity: 1;
    visibility: visible;
  }

  /* The sibling fade-out at my_css.css:822 dims .navbar-link only, so the
     wordmark would stay bright while every link around it dropped to 0.25.
     :has() rather than a sibling combinator because the wordmark PRECEDES
     .navbar-list in the DOM, and rather than :hover on the container because
     that would dim on the container's empty middle too. Browsers without
     :has() simply never dim it -- a lost flourish, not a broken navbar. */
  .lh.has-docked-nav .navbar > .container:has(.navbar-list:hover) .wordmark {
    opacity: .25;
  }
  .lh.has-docked-nav .wordmark:hover {
    opacity: 1;
  }

  .lh .navbar-list {
    margin-left: auto;
  }

}


/* ================ 5. Title block ================
   Two fields on one rule: last update at the left edge, views at the right.
   They sit at opposite ends rather than bunched left because that is the
   idiom this page already uses twice for a labelled pair on a rule --
   .section-header-row (my_css.css:293) and .school-role-header (:611) are
   both baseline-aligned space-between rows. No cell borders: with two fields
   a divider between them would be a tick floating in the middle of the rule. */
.lh .footer {
  border-top: 0;
  text-align: left;
  margin-top: 4rem;
  padding: 0 0 40px;
}

.lh .titleblock {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 24px;
  border-top: 1.5px solid var(--border-mid);
  padding-top: 12px;
}

.lh .titleblock > .tb:last-child {
  text-align: right;
}

/* Same micro-label convention as .navbar-link. */
.lh .tb .k {
  display: block;
  text-transform: uppercase;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .2rem;
  line-height: 1.4;
  color: var(--text-faint);
  margin-bottom: 5px;
  white-space: nowrap;
}

.lh .tb .v {
  display: block;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .04rem;
  line-height: 1.45;
  color: var(--text-mid);
  white-space: nowrap;
}

/* my_css.css:733 already gives #view-count weight 600 and --text-soft; only the
   size and figure alignment need to join the other field. */
.lh .tb .v#view-count {
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* Phones: stack the two fields instead of setting them at opposite ends.
   Both keys are nowrap by necessity -- a key that wraps drops its own value a
   line while its neighbour's stays put, and the row of values stops reading as
   a row -- so side by side they simply cannot fit. Measured before this rule,
   the pair needed 362px against a 340px row at 380px wide, which pushed the
   whole DOCUMENT into sideways scroll, not just the footer. */
@media (max-width: 549px) {
  .lh .titleblock {
    display: block;
    padding-top: 10px;
  }

  .lh .titleblock > .tb {
    margin-top: 14px;
  }

  .lh .titleblock > .tb:first-child {
    margin-top: 0;
  }

  /* The right-hand field is only right-aligned to sit at the far end of a rule
     it no longer shares. */
  .lh .titleblock > .tb:last-child {
    text-align: left;
  }
}

/* ================ 6. Scroll gauge ================
   Sits OUTSIDE .navbar in the markup, and is position:fixed in every state.
   That is the whole fix: while it lived inside .navbar > .container it was
   absolute against a ~960px container undocked and then switched to fixed at
   100% of a 1280px viewport on dock, so it jumped wider and slid left at the
   moment the bar pinned. Now the geometry never changes -- only the fill does.

   Ink (--text-strong), not an accent, so no new hue enters the page. Desktop
   only, matching .navbar, which my_css.css hides below 750px. */
.lh .nav-gauge {
  display: none;
}

@media (min-width: 750px) {
  .lh .nav-gauge {
    display: block;
    position: fixed;
    top: calc(5.2rem - 1px);   /* lands on the docked navbar's bottom border */
    left: 0;
    width: 100%;
    height: 2px;
    background: var(--text-strong);
    transform: scaleX(0);
    transform-origin: 0 50%;
    transform-box: border-box;
    pointer-events: none;
    z-index: 100;
    opacity: 0;
    transition: opacity 0.3s ease;
  }

  /* Visible only while the bar it belongs to is pinned -- which is also exactly
     the range it measures, so it fades in reading 0 rather than part-filled. */
  .lh.has-docked-nav .nav-gauge {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .lh .nav-gauge,
  .lh .wordmark { transition: none; }
}


/* ================ 7. Theme switch: keep the navbar in step ================
   my_js.js dissolves the whole old frame as ONE snapshot and suppresses every
   per-element transition while it runs (my_css.css:62-81), so styles all flip
   in the same frame -- measured, they do.

   The docked navbar still gave itself away, because it is the only element on
   the page with a backdrop-filter. A backdrop filter samples the COMPOSITED
   backdrop, and during the dissolve that backdrop still contains the fading
   old-theme snapshot -- so the bar is blurring a copy of the old page while
   everything around it is a straight crossfade between two opaque blocks. One
   band resolving on a different curve from its neighbours is exactly the thing
   the eye catches.

   Dropping the blur for the duration makes it composite like every other block.
   Only the filter is touched, deliberately: the navbar's own
   `transition: background 0.3s` (my_css.css:786) comes back to life the instant
   the guard class is removed, so forcing the background here would ease it back
   over 0.3s and reintroduce the very lag being fixed. backdrop-filter is not in
   that transition list, so it restores instantly and silently. */
html.theme-switching .lh .navbar {
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}
