/* ==========================================================================
   blankhall.com — editorial grid
   Two paper stocks, warm. Hierarchy still comes from weight, scale and space,
   never from colour: the only hue on the page is the paper itself, and it
   carries no meaning. --accent stays deliberately identical to --ink so a
   stray accent can never reintroduce a real tint. The page alternates between
   --paper and --paper-sunk as you scroll (see "page tone" below).
   ========================================================================== */

:root {
  color-scheme: light;

  --paper:        #FBF9F5;
  --paper-sunk:   #EFE8DA;
  --ink:          #14110C;
  --muted:        #6B6459;
  --rule:         #DCD4C4;
  --rule-strong:  #C0B6A2;
  --accent:       #14110C;
  --accent-wash:  rgba(0, 0, 0, 0.08);
  --shade:        rgba(0, 0, 0, 0.04);

  /* One sans does all the work. Display differs from body by weight and
     tracking, not by family — the Ollama treatment. */
  --font-display: "Public Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-body:    "Public Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-mono:    "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --display-weight:  500;
  --display-track:  -0.03em;

  --rail:    8.5rem;
  --gutter:  clamp(1.5rem, 4vw, 3.25rem);
  --measure: 62ch;
  --pad:     clamp(1.25rem, 5vw, 4rem);
  --maxw:    70rem;

  --step:      cubic-bezier(0.22, 0.61, 0.36, 1);
  --nav-h:     3.5rem;

  /* the directional underline, shared by every link on the page */
  --uline-dur:    0.875s;
  --uline-ease:   cubic-bezier(0.33, 0, 0.15, 1);
  --uline-offset: -0.15em;
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --paper:        #0C0B08;
  --paper-sunk:   #1A1712;
  --ink:          #F7F4ED;
  --muted:        #979089;
  --rule:         #322D25;
  --rule-strong:  #4B4539;
  --accent:       #F7F4ED;
  --accent-wash:  rgba(255, 255, 255, 0.12);
  --shade:        rgba(255, 255, 255, 0.05);
}

/* Light is the default whatever the system says. The palette only goes dark
   when the visitor asks for it with the toggle, which sets data-theme and
   remembers it -- there is deliberately no prefers-color-scheme branch. */

/* --- reset ---------------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 1.5rem);
}

body {
  margin: 0;
  background-color: var(--paper);
  transition: background-color 0.7s var(--step);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  line-height: 1.65;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { max-width: 100%; display: block; }
h1, h2, h3, h4 { margin: 0; font-weight: 400; }
p { margin: 0 0 1.15em; }
ul { margin: 0; padding: 0; list-style: none; }

::selection { background: var(--accent-wash); color: var(--ink); }

a { color: inherit; }

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

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-mono);
  font-size: 0.8125rem;
}
.skip:focus { left: 0; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

/* --- shared type ---------------------------------------------------------- */

.wrap {
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--pad);
}

/* --- the underline ---------------------------------------------------------
   One effect, every link on the page. The line wipes in from the left on hover;
   on exit it does NOT rewind — transform-origin flips to the right, so the left
   end keeps travelling the same direction until the line vanishes off the right
   edge. transform-origin is deliberately not transitioned, so that flip is
   instant and the direction reads as continuous.
   -------------------------------------------------------------------------- */

.uline,
.foot a {
  position: relative;
  text-decoration: none;
}
.uline::after,
.foot a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: var(--uline-offset);
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform var(--uline-dur) var(--uline-ease);
  pointer-events: none;
}
.uline:hover::after,
.uline:focus-visible::after,
.uline[aria-current="true"]::after,
.foot a:hover::after,
.foot a:focus-visible::after {
  transform: scaleX(1);
  transform-origin: left center;
}

@media (prefers-reduced-motion: reduce) {
  .uline::after, .foot a::after { transition: none; }
}

.label {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}


/* --- masthead ------------------------------------------------------------- */

/* The masthead is sticky and opaque, so it has to carry the same tone as the
   page under it -- left on --paper it reads as a bright bar sitting on a
   tinted page every time an even section comes round. */
.masthead {
  position: sticky;
  top: 0;
  z-index: 50;
  background-color: var(--paper);
  transition: background-color 0.7s var(--step);
}

.masthead-inner {
  height: var(--nav-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.brand {
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  text-decoration: none;
  white-space: nowrap;
  padding-block: 0.9rem;
}

.nav { display: flex; align-items: center; gap: clamp(0.6rem, 1.5vw, 1.4rem); }

/* ANRO's link behaviour, ported from precise_animated_link_footer.dart: the
   weight eases 400 → 500 over 1400ms on hover and holds at 500 while active.
   Colour never moves. Public Sans is variable, so the weight interpolates. */
.nav-link {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  font-size: 0.875rem;
  font-weight: 400;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
  padding: 0.35rem 0;
  white-space: nowrap;
  transition: font-weight 1.4s var(--step);
}
/* A hidden copy at the heavy weight fixes the box width, so the row cannot
   shuffle sideways while a link thickens. */
.nav-link::before {
  content: attr(data-label);
  font-weight: 500;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  pointer-events: none;
}
/* Weight marks the section you are actually in; hover is the underline alone. */
.nav-link[aria-current="true"] { font-weight: 500; }

.nav-tools { display: flex; align-items: center; gap: 0.25rem; }

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem; height: 2.75rem;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 0.2s var(--step);
}
.icon-btn:hover { opacity: 1; }
.icon-btn svg { width: 1.05rem; height: 1.05rem; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

.theme-btn .moon { display: none; }
:root[data-theme="dark"] .theme-btn .sun { display: none; }
:root[data-theme="dark"] .theme-btn .moon { display: block; }


.nav-toggle { display: none; }

@media (max-width: 900px) {
  .nav-toggle { display: inline-flex; }
  .nav {
    position: absolute;
    left: 0; right: 0;
    top: var(--nav-h);
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0.5rem var(--pad) 1.25rem;
    background: var(--paper);
    border-bottom: 1px solid var(--rule);
  }
  .nav[hidden] { display: none; }
  .nav-link {
    display: flex;
    padding: 0.85rem 0;
    font-size: 0.95rem;
    border-bottom: 1px solid var(--rule);
  }
  .nav-link::after { display: none; }
  .nav-link:last-child { border-bottom: 0; }
}

/* --- hero: the title page ------------------------------------------------- */

.hero {
  padding-block: clamp(2.5rem, 6vw, 4.5rem) clamp(2.75rem, 5.5vw, 4.5rem);
  text-align: center;
  position: relative;
}

.portrait {
  width: clamp(150px, 17vw, 220px);
  /* the width/height attributes on the img are presentational hints; without
     height:auto they win and aspect-ratio is ignored entirely */
  height: auto;
  aspect-ratio: 4 / 5;
  border-radius: 1.25rem;
  object-fit: cover;
  object-position: 50% 22%;
  margin: 0 auto clamp(1.5rem, 3vw, 2rem);
}

.hero h1 {
  font-family: var(--font-display);
  font-size: clamp(2rem, 5.5vw, 3.75rem);
  font-weight: var(--display-weight);
  line-height: 1.0;
  letter-spacing: var(--display-track);
  margin-bottom: clamp(0.9rem, 2vw, 1.35rem);
  text-wrap: balance;
}

.hero-tagline {
  font-size: clamp(1rem, 1.6vw, 1.15rem);
  line-height: 1.55;
  max-width: 46ch;
  margin: 0 auto 1.15rem;
  color: var(--muted);
  letter-spacing: -0.01em;
  text-wrap: pretty;
}

.hero-meta {
  margin-bottom: clamp(1.5rem, 3.5vw, 2rem);
  color: var(--muted);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  min-height: 2.75rem;
  padding: 0.7rem 1.4rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  border: 1px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
  transition: background-color 0.18s var(--step), color 0.18s var(--step), border-color 0.18s var(--step);
}
/* Both buttons invert on hover — the only two tones there are. */
.btn:hover { background: var(--paper); color: var(--ink); }

.btn-ghost {
  background: none;
  color: var(--ink);
  border-color: var(--rule-strong);
}
.btn-ghost:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

.btn svg { width: 0.85rem; height: 0.85rem; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }

/* --- ventures accordion ---------------------------------------------------
   The panel animates grid-template-rows 0fr -> 1fr rather than a max-height
   guess, so it is exact at any body length, and the name grows in step with it
   -- the same "this one is active now" gesture the trajectory heading uses.
   Opening one closes the other: with two ventures that keeps the section
   compact and means only one large name is on screen at a time.
   -------------------------------------------------------------------------- */

.acc-item {
  border-top: 1px solid var(--rule);
}
.acc-item:first-of-type { border-top-color: var(--rule-strong); }

.acc-head { margin: 0; }

.acc-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  width: 100%;
  appearance: none;
  background: none;
  border: 0;
  padding: clamp(1.25rem, 2.5vw, 1.75rem) 0;
  text-align: left;
  cursor: pointer;
  color: inherit;
}

.acc-trigger:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }

.acc-label { display: block; min-width: 0; }

.acc-meta {
  display: block;
  margin-bottom: 0.35rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  transition: color 0.5s var(--step);
}

.acc-name {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2.6vw, 1.6rem);
  font-weight: var(--display-weight);
  line-height: 1.06;
  letter-spacing: var(--display-track);
  color: var(--muted);
  transition: font-size 0.95s var(--step), color 0.95s var(--step);
}

.acc-trigger:hover .acc-name { color: var(--ink); }

.acc-item.is-open .acc-name {
  font-size: clamp(1.75rem, 4.5vw, 2.75rem);
  color: var(--ink);
}

/* two hairlines: a plus that becomes a minus */
.acc-icon {
  position: relative;
  flex: none;
  width: 0.9rem;
  height: 0.9rem;
  color: var(--muted);
}
.acc-icon::before,
.acc-icon::after {
  content: "";
  position: absolute;
  /* offset by half the stroke so the bars stay centred -- the rotation that
     turns the plus into a minus pivots on this centre, and a hairline that is
     a pixel high forgives being off by one where a 2px one does not */
  top: calc(50% - 1px);
  left: 0;
  width: 100%;
  height: 2px;
  background: currentColor;
  transition: transform 0.95s var(--step);
}
.acc-icon::after { transform: rotate(90deg); }
.acc-trigger:hover .acc-icon { color: var(--ink); }
.acc-item.is-open .acc-icon { color: var(--ink); }
.acc-item.is-open .acc-icon::after { transform: rotate(0deg); }

.acc-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.95s var(--step);
}

.acc-item.is-open .acc-panel { grid-template-rows: 1fr; }

.acc-panel-inner {
  overflow: hidden;
  min-height: 0;
  /* the body fades in behind the unfolding rather than being dragged into
     view with it -- on the way out it goes first, so the panel is already
     empty by the time it finishes closing */
  opacity: 0;
  transition: opacity 0.5s var(--step);
}

.acc-item.is-open .acc-panel-inner {
  opacity: 1;
  transition-duration: 0.7s;
  transition-delay: 0.25s;
}

/* the padding lives on a child so it collapses with the row, not against it */
.acc-panel-inner > :first-child { margin-top: 0; }
.acc-panel-inner > :last-child { margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }

/* --- section scaffold ----------------------------------------------------- */

/* No rule between sections: the alternating paper tone is the separator now,
   and a hairline sitting on the seam only fought it. */
.section { padding-block: clamp(2.5rem, 4.5vw, 3.75rem); }

.section-head,
.entry {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  column-gap: var(--gutter);
  row-gap: 0;
}

.section-head { margin-bottom: clamp(1.5rem, 2.75vw, 2.25rem); align-items: baseline; }

.sec-num {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  padding-top: 0.55rem;
}

.sec-title {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 4vw, 2.75rem);
  font-weight: var(--display-weight);
  line-height: 1.06;
  letter-spacing: var(--display-track);
}

.sec-note {
  grid-column: 2;
  margin: 0.85rem 0 0;
  max-width: var(--measure);
  color: var(--muted);
}

/* rule spans both columns because the two children sit side by side */
.entry > * {
  border-top: 1px solid var(--rule);
  padding-top: clamp(1.25rem, 2.5vw, 1.75rem);
  padding-bottom: clamp(1.5rem, 3vw, 2.25rem);
}
.entry:first-of-type > * { border-top-color: var(--rule-strong); }

.entry-meta { color: var(--muted); }
.entry-period {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  display: block;
}
.entry-place {
  display: block;
  margin-top: 0.4rem;
  font-size: 0.8125rem;
  color: var(--muted);
}

.entry-main { min-width: 0; }
.entry-title {
  font-family: var(--font-display);
  font-size: clamp(1.2rem, 2.4vw, 1.5rem);
  font-weight: var(--display-weight);
  line-height: 1.28;
  letter-spacing: -0.02em;
}
.entry-sub {
  margin: 0.35rem 0 0;
  color: var(--muted);
}
.entry-note {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0.55rem 0 0;
}

.points {
  margin: 1rem 0 0;
  max-width: var(--measure);
}
.points li {
  position: relative;
  padding-left: 1.35rem;
  margin-bottom: 0.6rem;
  color: var(--ink);
}
.points li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.72em;
  width: 0.5rem; height: 1px;
  background: var(--rule-strong);
}
.points em { font-style: italic; }

.prose { max-width: var(--measure); }

/* --- about ---------------------------------------------------------------- */

/* The measure goes on the paragraphs, never on the element carrying the rule —
   otherwise the hairline stops short of the column and the grid loses its edge. */
.about-text p { max-width: var(--measure); }

/* The two paragraphs trade sizes once, at a threshold -- not gradually as you
   scroll. Sliding the sizes continuously meant neither paragraph was ever quite
   a lead or quite body text; it just looked like the type was drifting. So the
   sizes are two fixed states, and crossing the threshold transitions between
   them while the text dips and comes back, so the change reads as a fade rather
   than a jump. main.js flips .is-swapped, with hysteresis, so scroll jitter at
   the threshold cannot flutter it back and forth. */
/* About runs longer than it reads. The size switch fires once the paragraphs
   have climbed most of the way up the screen, and with ordinary padding the
   next section had already taken over the lower half of the viewport by then --
   the switch happened somewhere you were no longer looking. This is the scroll
   room the effect needs, and it lands in the About tone band, so it reads as a
   pause before Ventures rather than a hole. */
.section-about { padding-bottom: clamp(4.5rem, 37vh, 22rem); }

.about-text {
  /* A deliberate ramp rather than a clamp that sits on its floor. 1.7vw only
     cleared the 1.3rem floor above ~1220px, which left the lead pinned flat
     across every phone and most laptops and made the size switch invisible
     there. This crosses its whole range between roughly 400 and 830px, so the
     lead is 20.8px on a phone, 22.4px on a desktop, and genuinely fluid in
     between -- always a clear step above the 17px body. */
  --lead: clamp(1.3rem, 1.18rem + 0.42vw, 1.4rem);
  --body: 1.0625rem;
}

.about-text p {
  transition: font-size 1.6s var(--step), opacity 0.8s var(--step);
}

.about-text p:first-child {
  font-size: var(--lead);
  line-height: 1.5;
  letter-spacing: -0.015em;
  max-width: 54ch;
}

.about-text p + p {
  margin-top: 1.15rem;
  font-size: var(--body);
  line-height: 1.5;
  letter-spacing: -0.015em;
  max-width: 58ch;
}

.about-text.is-swapped p:first-child { font-size: var(--body); }
.about-text.is-swapped p + p { font-size: var(--lead); }

/* the dip that carries the switch */
.about-text.is-changing p { opacity: 0.3; }

/* --- ventures ------------------------------------------------------------- */

.out {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 1.15rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  color: var(--accent);
  text-decoration: none;
}
/* The link is ~19px of type; the tap target has to be 44. ::before grows the hit
   area, leaving ::after free to carry the shared underline. -12px left it a
   fraction short of 44 once the type settled, so it is -13. */
.out::before { content: ""; position: absolute; inset: -13px -8px; }

/* same trick as .out: the wordmark is only ~30px wide, so it gets a hit area
   that clears the minimum without changing where anything sits */
.brand { position: relative; }
.brand::before { content: ""; position: absolute; inset: -6px -10px; }
.out svg { width: 0.7rem; height: 0.7rem; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

.inline-links { display: flex; flex-wrap: wrap; gap: 0.4rem 1.25rem; margin-top: 1rem; }

/* Ventures, Trajectory and Research all drop the underline: these sections are
   already full of rules, and a second line under every reference read as
   clutter rather than as an affordance. The arrow does the work instead -- it
   sits slightly retracted and travels out along its own diagonal on hover, so
   it reads as the arrow doing what it is drawn to do rather than a generic
   nudge. Selected work keeps the underline: its links sit inside cards whose
   whole surface is the hover target, and the line is what confirms it. */
.acc-panel .out::after,
.traj-panel .out::after,
.research-item .out::after,
.work-item .out::after { content: none; }

.acc-panel .out svg,
.traj-panel .out svg,
.research-item .out svg,
.work-item .out svg {
  transform: translate(-1px, 1px);
  transition: transform 0.45s var(--step);
}

.acc-panel .out:hover svg,
.acc-panel .out:focus-visible svg,
.traj-panel .out:hover svg,
.traj-panel .out:focus-visible svg,
.research-item .out:hover svg,
.research-item .out:focus-visible svg,
.work-item .out:hover svg,
.work-item .out:focus-visible svg,
/* the whole card is the target, so hovering anywhere sends the arrow */
.work-item:hover .out svg {
  transform: translate(3px, -3px);
}

/* --- selected work: a snap rail -------------------------------------------
   Fourteen projects will not sit on one screen as a grid, so they run as one
   row on a native scroll-snap track: three in view, driven by the buttons, by
   drag, or by the keyboard on the track itself. Deliberately NOT the pinned
   wipe that section 03 uses -- that gesture is the page's signature and stops
   being one if it happens twice. This one never takes the page scroll.
   -------------------------------------------------------------------------- */

.rail { --rail-gap: clamp(1.25rem, 2.4vw, 2.25rem); }

.rail-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 0.9rem;
}

.rail-count {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* The buttons flank the track. Below 1150px they take their space from the row
   -- there is no margin left to put them in -- and above it they move out into
   the page margin so the track keeps the same left edge as Ventures, Research
   and About. */
.rail-frame {
  display: flex;
  align-items: center;
  gap: clamp(0.5rem, 1.2vw, 1rem);
}

.rail-frame .rail-track { flex: 1 1 auto; min-width: 0; }
.rail-frame .rail-btn { flex: none; }

@media (min-width: 1150px) {
  .rail-frame { position: relative; display: block; }
  .rail-frame .rail-btn {
    position: absolute;
    top: 50%;
    margin-top: -1.375rem;              /* half the button, no transform */
  }
  .rail-frame .rail-btn[data-dir="-1"] { left: -3.85rem; }
  .rail-frame .rail-btn[data-dir="1"]  { right: -3.85rem; }
}

.rail-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px is the touch-target floor; these were 40 */
  width: 2.75rem;
  height: 2.75rem;
  appearance: none;
  background: none;
  border: 1px solid var(--rule-strong);
  color: var(--ink);
  cursor: pointer;
  transition: background-color 0.25s var(--step), color 0.25s var(--step),
              border-color 0.25s var(--step), opacity 0.25s var(--step);
}

.rail-btn svg {
  width: 1rem;
  height: 1rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.rail-btn:hover:not(:disabled) {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.rail-btn:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* Invisible at the ends rather than dimmed -- but still occupying its slot, so
   nothing reflows and the other button never slides under the cursor. */
.rail-btn:disabled {
  opacity: 0;
  pointer-events: none;
  cursor: default;
}

.rail-track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - 2 * var(--rail-gap)) / 3);
  gap: var(--rail-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.rail-track::-webkit-scrollbar { display: none; }
.rail-track:focus-visible { outline: 2px solid var(--ink); outline-offset: 6px; }
.rail-track > * { scroll-snap-align: start; }

/* every card opens the row, so they all take the heavier rule */
.work-item {
  display: grid;
  grid-template-columns: 2.25rem minmax(0, 1fr);
  column-gap: clamp(0.85rem, 1.5vw, 1.35rem);
  border-top: 1px solid var(--rule-strong);
  padding-top: clamp(1rem, 1.8vw, 1.35rem);
  padding-bottom: clamp(1.25rem, 2vw, 1.6rem);
  min-width: 0;
}

.work-index {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  padding-top: 0.12rem;
  transition: color 0.35s var(--step);
}

.work-body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.work-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem 0.5rem;
  margin: 0 0 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.work-meta .role,
.research-meta .role { letter-spacing: 0; font-family: var(--font-body); font-size: 0.8125rem; }

/* --- research: three per row ----------------------------------------------
   Same card language as the work rail -- a ruled column, no box and no shadow.
   Three across is narrow, so the gutter tightens and the title steps down a
   size; below 860px three columns stop being readable and it becomes one.
   -------------------------------------------------------------------------- */

.research-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: clamp(1.25rem, 2.4vw, 2.25rem);
}

.research-item {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--rule-strong);
  padding-top: clamp(1rem, 1.8vw, 1.35rem);
  padding-bottom: clamp(0.75rem, 1.2vw, 1rem);
  min-width: 0;
}

/* Stacked, not the wrapping row the work cards use. At a third of the measure
   one of these degrees wraps to two lines and the others do not, which starts
   that card's title a line lower than its neighbours; forcing two lines
   everywhere keeps the three titles on one baseline. */
.research-meta {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin: 0 0 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.research-item .entry-title { font-size: clamp(1.05rem, 1.5vw, 1.2rem); }
.research-item .prose { margin-top: 0.55rem; max-width: none; }

/* the cards stretch to a common height, so pushing the links down lands all
   three link rows on the same line instead of wherever the body happens to end */
.research-item .inline-links {
  margin-top: auto;
  padding-top: 0.75rem;
}
.research-item .inline-links .out { margin-top: 0; }

@media (max-width: 860px) {
  .research-grid { grid-template-columns: minmax(0, 1fr); }
  .research-item { border-top-color: var(--rule); }
  .research-item:first-of-type { border-top-color: var(--rule-strong); }
}

.work-item .entry-title { font-size: clamp(1.05rem, 1.5vw, 1.25rem); }
.work-item .prose { margin-top: 0.5rem; max-width: none; }
.work-body .out { margin-top: auto; padding-top: 0.9rem; }

.work-item:hover .work-index { color: var(--ink); }

/* how far along the row you are, at a glance */
.rail-progress {
  position: relative;
  height: 2px;
  margin-top: 1.1rem;
  background: var(--rule);
}

.rail-progress span {
  position: absolute;
  inset: 0 auto 0 0;
  width: 100%;
  background: var(--ink);
  transform: scaleX(var(--rp, 0.2));
  transform-origin: left center;
  transition: transform 0.25s var(--step);
}

@media (max-width: 1000px) {
  .rail-track { grid-auto-columns: calc((100% - var(--rail-gap)) / 2); }
}

@media (max-width: 720px) {
  .rail-track { grid-auto-columns: 100%; }

  /* Flanking the track costs ~120px of a 335px row, and the arrow that is
     invisible at the ends still holds its slot -- which pushed the card out of
     line with the section title and squeezed the copy. Below this width the
     track gets the full row and the buttons drop underneath it, right-aligned. */
  .rail-frame {
    display: grid;
    grid-template-columns: 1fr auto auto;
    grid-template-areas:
      "track track track"
      ".     prev  next";
    row-gap: 0.85rem;
    column-gap: 0.5rem;
  }

  .rail-frame .rail-track { grid-area: track; }
  .rail-frame .rail-btn[data-dir="-1"] { grid-area: prev; }
  .rail-frame .rail-btn[data-dir="1"] { grid-area: next; }

  /* The hanging index costs ~50px of a 335px row and takes the body down to 32
     characters a line. One card is on screen at a time here and the counter
     above already says which -- so the number goes and the copy gets the width. */
  .work-item { grid-template-columns: minmax(0, 1fr); }
  .work-index { display: none; }
}

/* --- languages ------------------------------------------------------------ */

.langs { margin-top: 0.5rem; max-width: 34rem; }
.lang {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--rule);
}
.lang:last-child { border-bottom: 0; }
.lang-name { font-size: 1.125rem; font-weight: var(--display-weight); letter-spacing: -0.015em; }
.lang-right { display: flex; align-items: baseline; gap: 0.75rem; }
.lang-level { color: var(--muted); font-size: 0.9375rem; }
.cert {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid var(--accent);
  padding: 0.15rem 0.4rem;
  white-space: nowrap;
}

/* --- trajectory: tabs over a scroll-driven horizontal track ---------------
   The travel is an empty ::after block after the sticky child, so the sticky
   child stays pinned for exactly that distance. It has to be generated
   CONTENT and not padding: a sticky box is constrained to its parent's
   content box, so padding-bottom on .traj extends the page without extending
   the range the pin actually holds for -- it scrolls away mid-section.
   Done this way the track is free to be as tall as its tallest panel with no
   measurement in JS, and nothing is recomputed on resize.
   -------------------------------------------------------------------------- */

.traj {
  --tp: 0;                 /* 0 -> 2, one per panel. written by main.js */
  --traj-travel: 260vh;    /* ~1.3 screens per transition */
  --traj-top: calc(var(--nav-h) + 0.35rem);
}

/* The pin is ALWAYS sticky, and only the spacer is conditional. With no
   spacer the parent's content box is exactly the pin, so the sticky range is
   zero and the element behaves as if it were static -- which is the unpinned
   mode. Keeping it sticky in both modes means getComputedStyle(pin).top
   always resolves to px, so JS can read the offset the CSS chose instead of
   re-deriving it and drifting from this file. */
.traj-pin {
  position: sticky;
  top: var(--traj-top);
}

/* The pin fills the screen and the frame takes whatever the header leaves, with
   the track centred inside it. The header still sits at the top -- but the
   leftover height is now shared above and below the panels instead of dumping
   240px of void under them. Track height is the tallest panel either way, so
   nothing shifts vertically as the wipe runs. */
.traj.is-pinned .traj-pin {
  min-height: calc(100vh - var(--traj-top));
  display: flex;
  flex-direction: column;
}

.traj.is-pinned .traj-viewport {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
}

.traj.is-pinned .traj-track { width: 100%; }

/* The section head rides inside the pin so the title and the tabs stay at the
   top of the screen for the whole wipe. It pays for that in pin height, which
   the frame below has to fit inside, so it is tighter here than the heads of
   the other sections. */
.traj-pin .section-head {
  margin-bottom: clamp(0.4rem, 2.5vh, 1.4rem);
}

/* At rest this title is exactly .sec-title -- same size as every other section
   head on the page. It only shrinks once the section actually takes the
   scroll, and animates between the two states. Note the pin is measured while
   UNLOCKED, so the fit guard is conservative by the height the title is about
   to give back: it can refuse to pin in a ~20px band of window heights where
   the locked layout would have fitted. That is the safe direction to be wrong. */
.traj-pin .sec-title,
.traj-pin .section-head {
  transition: font-size 0.55s var(--step),
              letter-spacing 0.55s var(--step),
              margin-bottom 0.55s var(--step),
              /* this rule outranks .reveal on the head, and transition is a
                 shorthand -- without these two the reveal would snap */
              opacity 1.1s var(--step),
              transform 1.1s var(--step);
}

.traj.is-locked .sec-title {
  font-size: clamp(1.25rem, 3.4vh, 2.1rem);
  letter-spacing: -0.015em;
}

.traj.is-locked .section-head {
  margin-bottom: clamp(0.45rem, 2.2vh, 1.5rem);
}

.traj-pin .sec-num { padding-top: 0.35rem; }

.traj.is-pinned::after {
  content: "";
  display: block;
  height: var(--traj-travel);
}

/* Air above and around the pinned header, bought only out of height the window
   actually has. The base values above stay tight on purpose: they are what
   decides whether the frame fits at all, so spending on them would raise the
   threshold and cost short windows the effect entirely. */
@media (min-height: 780px) {
  .traj { --traj-top: calc(var(--nav-h) + 1.25rem); }
  .traj-pin .section-head { margin-bottom: 1.6rem; }
  .traj-tabs { margin-bottom: 1.35rem; }
}

@media (min-height: 900px) {
  .traj { --traj-top: calc(var(--nav-h) + 2.25rem); }
  .traj-pin .section-head { margin-bottom: 2.1rem; }
  .traj-tabs { margin-bottom: 1.75rem; }
  .traj-panel .entry > * { padding-top: 1.15rem; padding-bottom: 1.35rem; }
}

/* tabs --------------------------------------------------------------------- */

.traj-tabs {
  position: relative;
  display: flex;
  margin-bottom: clamp(0.45rem, 1.8vh, 1rem);
  border-bottom: 1px solid var(--rule-strong);
}

.traj-tab {
  flex: 1 1 0;
  min-width: 0;
  appearance: none;
  background: none;
  border: 0;
  padding: 0 0 clamp(0.4rem, 1.3vh, 0.75rem);
  text-align: center;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color 0.3s var(--step);
}

.traj-tab:hover { color: var(--ink); }
.traj-tab[aria-selected="true"] { color: var(--ink); }
.traj-tab:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }

/* The active rule slides continuously with the track instead of snapping from
   tab to tab, so the underline and the panels read as one gesture. Tabs are
   flex: 1 so all three thirds are equal and the rule can be a plain third. */
.traj-line {
  position: absolute;
  left: 0;
  bottom: -1px;
  width: calc(100% / 3);
  height: 2px;
  background: var(--ink);
  transform: translate3d(calc(var(--tp) * 100%), 0, 0);
}

/* track -------------------------------------------------------------------- */

.traj-viewport { overflow: hidden; }

.traj-track {
  display: flex;
  align-items: flex-start;
  transform: translate3d(calc(var(--tp) * -100%), 0, 0);
}

.traj-panel {
  flex: 0 0 100%;
  min-width: 0;
}

/* The pinned frame has to fit the viewport in one piece: anything below the
   fold in here is unreachable, because scrolling drives the wipe instead of
   revealing it. So entries sit tighter than they do elsewhere on the page,
   and the last one drops its trailing space entirely. main.js refuses to pin
   at all if the frame still does not fit. */
/* The rail here was squeezed to 6.5rem back when the frame was fighting for
   vertical room. It is not any more -- the pin fills the screen and the frame
   is centred in it -- and the squeeze was showing: at 104px every place name
   broke across two lines, and the column no longer lined up with the identical
   rail in every other section. Back to the page's own measure. */
.traj-panel .entry {
  grid-template-columns: var(--rail) minmax(0, 1fr);
  column-gap: var(--gutter);
}

/* The languages panel has no rail content, so its half of the rule was a
   hairline hanging in empty space with nothing under it -- it read as a
   leftover, not as structure. The column still holds its place so the panels
   keep a common left edge; only the rule goes. */
.traj-panel .entry-meta:empty { border-top-color: transparent; }

/* and the list fills the column it sits in: capped at 34rem it stopped well
   short of the rule above it, which made the panel look half-finished */
.traj-panel .langs { max-width: none; }

.traj-panel .entry > * {
  padding-top: clamp(0.4rem, 1.7vh, 0.95rem);
  padding-bottom: clamp(0.45rem, 2vh, 1.1rem);
}

.traj-panel .entry:last-of-type > * { padding-bottom: 0; }

/* The bullet lists are most of a panel's height, so they set the ceiling on
   what can stay pinned. Everything in here is sized in vh rather than vw: the
   constraint is the height of the window, so a short window tightens the frame
   instead of pushing its bottom past the fold and losing the pin entirely.
   Type size is left alone -- only leading and gaps give. */
.traj-panel .points { margin-top: clamp(0.3rem, 1.1vh, 0.6rem); }

.traj-panel .points li {
  margin-bottom: clamp(0.15rem, 0.62vh, 0.35rem);
  line-height: clamp(1.25rem, 2.9vh, 1.6rem);
}

.traj-panel .entry-sub { margin-top: 0.2rem; }

/* .out carries its own margin-top for when it stands alone, and .inline-links
   adds another above the row. Stacked, one line of mono text costs ~38px twice
   over -- the single largest non-text item in the frame. Inside a panel the
   row's margin is enough. */
.traj-panel .inline-links { margin-top: clamp(0.35rem, 1.2vh, 1rem); }
.traj-panel .inline-links .out { margin-top: 0; }
.traj-panel .langs { line-height: clamp(1.25rem, 2.9vh, 1.6rem); }

/* The panels move sideways, so a viewport IntersectionObserver never fires for
   the two that are translated off-frame -- they would stay invisible forever.
   main.js marks each panel shown as the wipe brings it in, gated on the section
   itself having been reached, so panel one does not quietly reveal at page load
   while it is still far below the fold. */
.traj-panel .entry {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 1.1s var(--step), transform 1.1s var(--step);
}

.traj-panel.is-shown .entry { opacity: 1; transform: none; }

/* No pin: narrow screens, reduced motion, and no-JS all land here. The track
   becomes a native snap scroller, so the tabs still work by tap and the panels
   still work by swipe -- the feature degrades rather than disappearing. */
.traj:not(.is-pinned) .traj-viewport {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
}

.traj:not(.is-pinned) .traj-viewport::-webkit-scrollbar { display: none; }
.traj:not(.is-pinned) .traj-track { transform: none; }
.traj:not(.is-pinned) .traj-panel { scroll-snap-align: start; }

/* --- skills --------------------------------------------------------------- */

.skill-group > * {
  border-top: 1px solid var(--rule);
  padding-top: 1.15rem;
  padding-bottom: 1.4rem;
}
.skill-name {
  font-size: 1.0625rem;
  font-weight: var(--display-weight);
  letter-spacing: -0.015em;
  line-height: 1.35;
}
.skill-items { color: var(--muted); max-width: var(--measure); margin: 0; }

/* --- contact -------------------------------------------------------------- */

/* Two columns of ruled cells rather than one long list, so the last section
   fills its measure instead of trailing down the left. Five items means the
   CV cell sits alone on the final row -- deliberate, not a gap to fill. */
.contact-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--gutter);
}

.contact-row {
  display: block;
  padding: 1rem 0;
  border-top: 1px solid var(--rule);
  text-decoration: none;
  transition: color 0.18s var(--step);
}

.contact-head {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  margin-bottom: 0.4rem;
}

.contact-mark {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  fill: currentColor;
  color: var(--muted);
  transition: color 0.18s var(--step);
}

/* no underline on these -- hover reads through the mark and label darkening */
.contact-row:hover .contact-mark,
.contact-row:focus-visible .contact-mark,
.contact-row:hover .contact-kind,
.contact-row:focus-visible .contact-kind { color: var(--ink); }

.contact-kind {
  flex: none;
  transition: color 0.18s var(--step);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.contact-value {
  display: inline-block;
  font-size: clamp(1.0625rem, 2.2vw, 1.35rem);
  font-weight: var(--display-weight);
  letter-spacing: -0.02em;
  word-break: break-word;
}

/* The wrapping .entry already draws a rule across the top of the section, so
   the first row of cells would stack a second one 29px under it. Drop theirs
   and let the entry rule open the grid. */
.contact-row:nth-child(-n + 2) { border-top: 0; padding-top: 0; }

@media (max-width: 720px) {
  .contact-list { grid-template-columns: minmax(0, 1fr); }
  /* one column: only the very first cell sits under the entry rule */
  .contact-row:nth-child(2) {
    border-top: 1px solid var(--rule);
    padding-top: 1rem;
  }
}

/* --- footer --------------------------------------------------------------- */

.foot {
  padding-block: clamp(3rem, 5vw, 4.5rem) 3.5rem;
  color: var(--muted);
}
.foot-inner {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  justify-content: space-between;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
}
.foot a { color: var(--muted); }

/* --- narrow --------------------------------------------------------------- */

@media (max-width: 720px) {
  :root { --rail: 1fr; }

  .section-head,
  .entry,
  .skill-group {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }
  .section-head { gap: 0.5rem; }
  .sec-num { padding-top: 0; }
  .sec-note { grid-column: 1; }

  .entry > *,
  .skill-group > * { border-top: 0; padding-top: 0; padding-bottom: 0; }
  .entry > .entry-meta,
  .skill-group > .skill-name {
    border-top: 1px solid var(--rule);
    padding-top: 1.25rem;
    padding-bottom: 0.6rem;
  }
  .entry > .entry-main,
  .skill-group > .skill-items { padding-bottom: 1.75rem; }

  .entry-period,
  .entry-place { display: inline; margin-top: 0; }
  .entry-place::before { content: " · "; color: var(--rule-strong); }
  .contact-kind { flex-basis: 5rem; }

  /* .traj-panel .entry outranks the .entry collapse above, so the trajectory
     panels kept their two columns here -- and since --rail is 1fr on mobile
     that came out as two equal halves, squeezing the bullets to ~155px and
     breaking them to a word or two a line. They collapse like the rest now. */
  .traj-panel .entry {
    grid-template-columns: minmax(0, 1fr);
    column-gap: 0;
  }

  /* the languages panel's rail is empty; on one column it has nothing to hold
     a place for, so it would just be a rule over a gap */
  .traj-panel .entry-meta:empty { display: none; }

  /* the tabs are the only way through this section on a phone, and at 25px
     tall they were half the minimum target */
  .traj-tab {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 2.75rem;
  }
}

/* --- motion --------------------------------------------------------------- */

/* --- page entrance --------------------------------------------------------
   Pure CSS on purpose. Anything that hides the page and waits for script to
   reveal it leaves a blank screen when the script fails; this degrades to a
   fully visible page instead. The hero has no .reveal because it is above the
   fold with nothing to scroll into -- it enters on load, top to bottom.
   -------------------------------------------------------------------------- */

@keyframes screen-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

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

body { animation: screen-in 0.85s var(--step) both; }

/* opacity only, never transform: the masthead is position: sticky and a
   transform on it would give it a containing block of its own */
.masthead {
  animation: screen-in 0.9s var(--step) both;
  animation-delay: 0.05s;
}

.portrait,
.hero h1,
.hero-tagline,
.hero-meta,
.hero-actions {
  animation: rise-in 1.05s var(--step) both;
}

.portrait     { animation-delay: 0.18s; }
.hero h1      { animation-delay: 0.34s; }
.hero-tagline { animation-delay: 0.50s; }
.hero-meta    { animation-delay: 0.62s; }
.hero-actions { animation-delay: 0.74s; }

/* --- hero portrait on hover (wide desktop only) ---------------------------
   Hidden until you hover the name. It is taken out of flow and parked in the
   empty margin beside the name rather than left in place above it: holding its
   old slot open reserved ~380px of blank above the name, which read as an
   image that had failed to load. Out of flow it costs no space at all, so the
   resting hero is just name, line, place, buttons -- and revealing it cannot
   move the name, which matters, because if the name moved the pointer would
   fall off it and the reveal would flicker shut.

   Gated three ways: :has() has no previous-sibling fallback, so where it is
   missing the portrait stays plainly visible instead of becoming unreachable;
   (hover: hover) keeps it away from touch, which has no hover to give; and
   1100px is where the margin beside the name is actually wide enough to hold
   it. Narrower than that, and on mobile, the portrait sits in flow as before.
   -------------------------------------------------------------------------- */

@supports selector(:has(*)) {
  @media (hover: hover) and (min-width: 1100px) {
    .hero .wrap { position: relative; }

    .portrait {
      position: absolute;
      /* .wrap carries padding-inline, and absolute offsets resolve against the
         padding box -- left: 0 parks it outside the content column, which runs
         off-screen once the wrap stops being narrower than the viewport */
      left: var(--pad);
      top: 44%;
      width: clamp(150px, 13vw, 195px);
      margin: 0;
      /* the load stagger sets opacity 1 with fill: both, which would win over
         the hidden state below -- so the portrait sits out the entrance here */
      animation: none;
      opacity: 0;
      transform: translateY(-50%) translateX(-14px) scale(0.97);
      transition: opacity 1s var(--step), transform 1s var(--step);
      pointer-events: none;
    }

    .hero:has(h1:hover) .portrait,
    .hero:focus-within .portrait {
      opacity: 1;
      transform: translateY(-50%) translateX(0) scale(1);
    }
  }
}

.reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity 1.1s var(--step), transform 1.1s var(--step);
}
.reveal.in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .reveal, .traj-panel .entry { opacity: 1; transform: none; }
}

/* --- print ---------------------------------------------------------------- */

@media print {
  :root { --paper: #fff; --ink: #000; --muted: #333; --rule: #bbb; --accent: #000; }
  .masthead, .hero-actions, .foot, .skip { display: none !important; }
  body { font-size: 10.5pt; }
  .section { padding-block: 1rem; break-inside: avoid; }
  .entry { break-inside: avoid; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 8pt; color: #555; }
}
