/* ==========================================================================
   Events page (/events), the portfolio one-pager. Loaded only on /events; the
   <body> also carries .page-portfolio (views/pages/events.ejs). Tokens, the
   --th-* theme variables and the ax- utilities come from site.css (do not
   redefine them here).

   Layout = one page that scrolls normally, like pxpush.com:
     - the header is the slim frame (wordmark + tabs in the top row, the status
       strip below; no title row, no dashed bands), full width with the home gutters;
     - a tiny section index is fixed at the very left (events.js marks the current one);
     - every section is a full-width block with its OWN colour (palette tones only:
       yellow / white / blurple / dark grey / the light grey-blue of LORE, neighbours always
       differ, text >= 4.5:1: tests/portfolio-page.test.js computes it). Its text
       sits in a right-hand column, right-aligned;
     - the colour change is the pxpush one: the last 100vh of a section carries ten bands
       in the NEXT colour (.pf-wipe, z-index above the text); from the moment that
       overlay's top reaches the top of the screen until it has scrolled 80vh further the
       bands rise from the bottom one after the other (scrubbed by the scroll, events.js),
       so the whole screen ends in the new colour, then the next section starts in it;
     - the footer is fixed under the content and is uncovered only at the very end;
     - buttons are the pxpush link buttons (thin top rule, mono label at the left, an
       arrow at the right, a fill that rises on hover + a quick letter scramble);
     - the old pop-ups are inline expanders (.pf-more).
   Hooks for the next phases: .pf-stripe (stripe header slot of every section),
   #bhb-gallery (gallery slot), data-section-bg + --pf-dots (the dot colour of the
   section). Everything sits on the 8px grid; no shadows or radius.
   ========================================================================== */

/* ---------- the section colours: the five tones (they are --pf-bg-* in site.css) ---------- */

body.page-portfolio {
  --pf-wipe-h: 100vh;                  /* the overlay of a section change: a full screen high (pxpush: .overlay { height: 100vh }) */
  --pf-end-pad: max(200px, 40vh);      /* the empty page after the last content, under the overlay */
  --pf-wrap-pt: clamp(48px, 9vw, 160px);   /* the empty space above the text column; the section label sits at the same height */
}

/* per section: background, text, quiet text and rules, and the tone of the dot texture */
.event-section[data-section-bg="yellow"] {
  --pf-bg: var(--pf-bg-yellow);
  --pf-fg: var(--yh-ink);
  --pf-soft: var(--yh-ink);
  --pf-dots: var(--pf-dots-yellow);
}
.event-section[data-section-bg="white"] {
  --pf-bg: var(--pf-bg-white);
  --pf-fg: var(--yh-ink);
  --pf-soft: var(--yh-ink-soft);
  --pf-dots: var(--pf-dots-white);
}
.event-section[data-section-bg="blurple"] {
  --pf-bg: var(--pf-bg-blurple);
  --pf-fg: var(--pf-fg-blurple);
  --pf-soft: var(--pf-fg-blurple);
  --pf-dots: var(--pf-dots-blurple);
}
.event-section[data-section-bg="dark"] {
  --pf-bg: var(--pf-bg-dark);
  --pf-fg: var(--yh-paper-hi);
  --pf-soft: var(--pf-soft-dark);
  --pf-dots: var(--pf-dots-dark);
}
.event-section[data-section-bg="lore"] {
  --pf-bg: var(--pf-bg-lore);
  --pf-fg: var(--yh-ink);
  --pf-soft: var(--yh-ink);
  --pf-dots: var(--pf-dots-lore);
}

/* ---------- the header on this page: the SUNCYCLE 3 sky blue (#7da9d9, the owner's swatch) with a near-black ink ----------
   The slim header itself (wordmark, pxpush tabs, status strip) is the shared one of site.css ("The header"); this page only
   gives it its colours. The first section is yellow, so the header paints the same yellow and the two read as one surface; it
   stays yellow while the page scrolls under the section changes, as the header leaves with the top of the page. Ink on it
   (the contrast of every pair is computed in tests/header-colours.test.js); contact.css and submit.css override these. */

body.page-portfolio {
  --hdr-bg: var(--pf-hdr-sky);
  --hdr-paint: var(--pf-hdr-sky);
  --hdr-fg: var(--pf-hdr-ink);
  --hdr-soft: var(--pf-hdr-ink);
  --hdr-line: var(--pf-hdr-ink);
  --hdr-fill: var(--pf-hdr-ink);
  --hdr-fill-fg: var(--yh-paper-hi);
  --hdr-hover: var(--pf-hdr-ink);
  --hdr-led: var(--pf-hdr-ink);
  --hdr-cta-ring: var(--pf-hdr-ink);   /* the bright-blue call-to-action tab (1.4:1 on this blue) gets a dark hairline */
}

/* the header of this page is the shared one, in the flow at the very top (site.css): the page's static first piece, with the SUNCYCLE 3 picture
   under it, never behind it. It is there only when the page is scrolled all the way up: the page opens already scrolled down by its height
   (events.js, sc3Open), and scrolling down from it first holds at that opening layout for a little (events.js, "the hold"). */

/* anchors (the index links, the cards) glide; none under reduced motion (site.css switches scroll-behavior off) */
html:has(body.page-portfolio) {
  scroll-behavior: smooth;
}
/* the lightbox locks the page; portfolio-gallery.js pads the root by the scrollbar's width so nothing shifts */
html.pf-lock {
  overflow: hidden;
}

/* ---------- the footer: fixed under the content, uncovered only at the very end ----------
   Its colour is the page's (--ftr-*, site.css "footer"): the navy of /contact, the paper of the forms; the picker's band and
   /events' taskbar (below) set their own. A hairline on top keeps it apart from a page of the same colour. */

.page-portfolio .site-footer {
  position: fixed;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 0;
  margin: 0;
  padding-top: 16px;
  border-top: 1px solid var(--ftr-edge);
  background: var(--ftr-bg);
}
.page-portfolio .site-footer::before {
  display: none;
}
/* the content lies over it (every section paints its own background) and ends with the footer's height
   of empty page: events.js writes the measured height to --pf-foot-h (the fallback fits two rows) */
.pf-main {
  position: relative;
  z-index: 1;
  margin-bottom: var(--pf-foot-h, 112px);
  overflow-x: clip;
}

/* ---------- the footer of /events: an old-Windows taskbar ----------
   views/partials/footer.ejs (variant 'taskbar') in the family of the LORE window (.pf-win): a SMALL bar (buttons 25px high, 9px labels) in a dusty steel
   blue (--pf-task-bar, grey-leaning, not the site's bright accent) with the light top edge (--pf-task-edge) of an XP taskbar, a raised grey «пуск» button on the left (to the home page), one grey button per section of the page
   (SUNCYCLE, BLEEDING HEARTS, DISCORD, PHYSICAL DROP, LORE: real in-page links; events.js presses the one in view and marks it
   aria-current="location"), and a sunken tray at the right with the five icon links and the clock (HH:MM, written by events.js; empty
   without it). The bevels are borders (white top-left, dark bottom-right; reversed when pressed); nothing blurs or glows. Like
   the other footers of this sheet it lies fixed UNDER the content (z-index 0, the rule above) and is uncovered only at the very end
   of the page: .pf-main ends with a footer-high margin (events.js measures the bar), so scrolling back up covers it again. Phones: the tray is a row of its own
   above the buttons, the buttons scroll sideways. The tones are --pf-task-* (site.css). */

.page-portfolio .site-footer--task {
  --ftr-bg: var(--pf-task-face);
  --ftr-paint: var(--pf-task-bar);
  --ftr-fg: var(--yh-ink);
  --ftr-soft: var(--yh-ink-soft);
  --ftr-edge: var(--pf-task-lo);
  --ftr-plate: var(--pf-task-face);
  --ftr-fill: var(--yh-ink);
  --ftr-fill-fg: var(--yh-paper-hi);
  --ftr-mark: var(--yh-accent-ink);
  display: flex;
  flex-wrap: nowrap;
  align-items: stretch;
  gap: 5px;
  padding: 3px 5px;
  border-top: 2px solid var(--pf-task-edge);
  background: var(--ftr-paint);
  color: var(--yh-ink);
}
.pf-task__start,
.pf-task__item {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  min-height: 25px;
  padding: 0 9px;
  border: 2px solid;
  border-color: var(--pf-task-hi) var(--pf-task-lo) var(--pf-task-lo) var(--pf-task-hi);
  background: var(--pf-task-face);
  box-shadow: inset -1px -1px 0 var(--pf-task-shade);
  color: var(--yh-ink);
  font-family: var(--hud-mono);
  font-size: 0.58rem;
  letter-spacing: 0.04em;
  line-height: 1.2;
  text-decoration: none;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.pf-task__start {
  flex: none;
  padding: 0 11px 0 8px;
  font-family: var(--hud-display);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  text-transform: lowercase;
}
.pf-task__logo {
  flex: none;
  width: 11px;
  height: 11px;
  fill: var(--yh-accent-ink);
}
.pf-task__items {
  display: flex;
  flex: 1 1 0;
  gap: 4px;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}
.pf-task__items::-webkit-scrollbar {
  display: none;
}
.pf-task__item {
  flex: 0 1 10.5rem;
  min-width: 5.2rem;
}
.pf-task__ico {
  flex: none;
  width: 11px;
  height: 9px;
  border: 1px solid var(--yh-ink);
  border-top-width: 2px;
  background: var(--pf-task-hi);
}
.pf-task__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pf-task__start:hover,
.pf-task__item:hover {
  background: var(--yh-paper-top);
}
/* pressed: the section in view (and the finger on it): the bevel reverses, the face turns the light blue of the title strip, the label sits down and goes bold */
.pf-task__item[aria-current="location"],
.pf-task__start:active,
.pf-task__item:active {
  border-color: var(--pf-task-lo) var(--pf-task-hi) var(--pf-task-hi) var(--pf-task-lo);
  background: var(--pf-task-lit);
  box-shadow: inset 1px 0 0 var(--pf-task-shade), inset 0 1px 0 var(--pf-task-shade);
  font-weight: 700;
}
.pf-task__item[aria-current="location"] .pf-task__label,
.pf-task__item:active .pf-task__label,
.pf-task__start:active .pf-task__start-label {
  transform: translate(1px, 1px);
}
/* keyboard: the dotted focus rectangle of the old shells, inside the button (the row clips anything outside it), ink on the face */
.pf-task__start:focus-visible,
.pf-task__item:focus-visible {
  outline: 2px dotted var(--yh-ink);
  outline-offset: -4px;
}
/* the tray: a sunken box with the icon row and the clock */
.pf-task__tray {
  display: flex;
  flex: none;
  align-items: center;
  gap: 8px;
  min-height: 25px;
  padding: 0 6px;
  border: 2px solid;
  border-color: var(--pf-task-lo) var(--pf-task-hi) var(--pf-task-hi) var(--pf-task-lo);
  background: var(--pf-task-face);
  color: var(--yh-ink);
}
.pf-task__tray .social-links {
  --ico: 21px;
  flex-wrap: nowrap;
  gap: 2px;
}
/* (the border is the face's own colour, not transparent: the sweep's checker layer repeats under a transparent border and would draw a dotted frame) */
.pf-task__tray .social-links a {
  border-color: var(--pf-task-face);
}
.pf-task__tray .social-links a:hover,
.pf-task__tray .social-links a:focus-visible {
  border-color: var(--ftr-fill);
}
.pf-task__tray .social-links svg {
  width: 14px;
  height: 14px;
}
.pf-task__clock {
  padding-left: 8px;
  border-left: 1px solid var(--pf-task-shade);
  font-family: var(--hud-mono);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}
.pf-task__clock:empty {
  display: none;
}
@media (max-width: 899px) {
  .pf-task__item { flex-basis: 8rem; }
}
@media (max-width: 719px) {
  .page-portfolio .site-footer--task {
    flex-wrap: wrap;
    gap: 3px;
    padding: 3px;
  }
  .pf-task__tray {
    order: -1;
    flex: 1 1 100%;
    justify-content: space-between;
    min-height: 24px;
  }
  .pf-task__tray .social-links {
    --ico: 22px;
  }
  .pf-task__clock {
    margin-left: auto;
  }
  .pf-task__item {
    flex: 0 0 auto;
    min-width: 0;
  }
}

/* ---------- the skip list: the way to the sections from the keyboard ----------
   The sections carry their own labels now (.pf-label); this list of in-page anchors is the keyboard / screen-reader way
   to jump between them. Invisible until one of its links has focus, then a small box in the top left corner. */

.pf-skip:not(:focus-within) {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.pf-skip:focus-within {
  position: fixed;
  top: 8px;
  left: 8px;
  z-index: 20;
  padding: 8px 12px;
  border: 1px solid var(--yh-ink);
  background: var(--yh-paper-hi);
}
.pf-skip__list {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  list-style: none;
}
.pf-skip a {
  color: var(--yh-ink);
  font-family: var(--hud-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  text-decoration: none;
  text-transform: uppercase;
}
.pf-skip a:focus-visible {
  outline: 2px solid var(--yh-ink);
  outline-offset: 2px;
  text-decoration: underline;
}

/* ---------- the sections ----------
   Geometry and type follow pxpush.com measured at 1440: the text column STARTS at about 40% of the page (right of the
   small index) and runs to the right gutter, left-aligned inside; the lead statement is big (2.8vw in its condensed face;
   here Inter Tight Light at 2.2vw, the same measure), the small copy is mono ~1.2vw, labels are mono 0.85vw uppercase
   with a tight tracking, titles are ~6vw (the same here). */

.event-section {
  --pf-pl: var(--yh-g);
  position: relative;
  min-height: var(--pf-wipe-h);
  padding: 0 var(--yh-g) var(--pf-end-pad) var(--pf-pl);
  overflow: clip;   /* the sticky top fade of a section stays inside it; clip is not a scroll container, so sticky still works */
  background: var(--pf-bg);
  color: var(--pf-fg);
  scroll-margin-top: 0;
}
.event-section:focus {
  outline: none;
}
.event-section:last-child {
  padding-bottom: clamp(72px, 14vh, 160px);
}
.event-section :focus-visible {
  outline: 2px solid var(--pf-fg);
  outline-offset: 2px;
}

/* the stripe header: the marquee rows of the section name (portfolio-stripes.js draws each row as a pixelated belt on a
   canvas). Geometry: exactly 2 tall rows (7.5vw high, caps ~75px at 1440: ~60% of pxpush's 120px `PX PUSH` rows, which the owner
   found too large to read here) and a BIG gap between them (3.2vw); full bleed, first in the section. Without the script
   the rows are plain typographic text, clipped at the edge. */
body.page-portfolio {
  --pf-stripe-rows: 2;
  --pf-stripe-row-h: clamp(48px, 7.5vw, 170px);
  --pf-stripe-gap: clamp(20px, 3.2vw, 72px);
  --pf-stripe-pad: clamp(24px, 3vw, 64px);
  --pf-stripe-h: calc(var(--pf-stripe-rows) * var(--pf-stripe-row-h) + (var(--pf-stripe-rows) - 1) * var(--pf-stripe-gap) + 2 * var(--pf-stripe-pad));
}
.pf-stripe {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--pf-stripe-gap);
  box-sizing: border-box;
  min-height: var(--pf-stripe-h);
  margin: 0 calc(-1 * var(--yh-g)) 0 calc(-1 * var(--pf-pl));
  padding: var(--pf-stripe-pad) 0;
  overflow: hidden;
  color: var(--pf-fg);
}
.pf-stripe__row {
  position: relative;
  display: block;
  flex: none;
  height: var(--pf-stripe-row-h);
  overflow: hidden;
  font-family: var(--hud-display);
  font-size: calc(var(--pf-stripe-row-h) * 0.8);
  font-weight: 300;
  line-height: var(--pf-stripe-row-h);
  text-transform: uppercase;
  white-space: nowrap;
}
.pf-stripe__cv {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  image-rendering: crisp-edges;
  image-rendering: pixelated;
}
/* drawn: the bitmap replaces the plain row (the text stays in the markup, it is only not painted) */
.pf-stripe--live .pf-stripe__row {
  color: transparent;
}
.pf-stripe::after {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  height: 1px;
  background: var(--pf-soft);
  transform: scaleX(var(--sep, 1));
  transform-origin: 0 50%;
}

/* the text column: from ~40% of the page to the right gutter, left-aligned */
.pf-wrap {
  width: min(100%, max(36rem, calc(60vw - var(--yh-g))));
  margin-left: auto;
  padding-top: var(--pf-wrap-pt);
  text-align: left;
}
/* the section label (pxpush's `● Nº001 / INTRO` in the left third of a section): it belongs to its section, scrolls with it,
   sits in the left column at the height of the first heading, in the section's ink, and its words come in by the scroll
   like the other small labels (events.js). The bullet is plain CSS, as there. */
.pf-label {
  position: absolute;
  top: calc(var(--pf-stripe-h) + var(--pf-wrap-pt));
  left: var(--yh-g);
  z-index: 1;
  max-width: calc(40vw - var(--yh-g) - 24px);
  margin: 0;
  padding: 0.55em 0 0 clamp(20px, 3vw, 48px);
  color: var(--pf-fg);
  font-family: var(--hud-mono);
  font-size: clamp(0.72rem, 0.85vw, 1rem);
  letter-spacing: -0.05vw;
  line-height: 1.2;
  text-transform: uppercase;
}
.pf-label::before {
  content: "";
  position: absolute;
  top: 0.5em;
  left: 0;
  width: 0.8em;
  height: 0.8em;
  background: currentColor;
  clip-path: circle(50%);
}
.pf-title {
  margin: 0 0 clamp(32px, 5vw, 88px);
  color: var(--pf-fg);
  font-family: var(--hud-display);
  font-size: clamp(2.2rem, 6vw, 8.6rem);
  font-weight: 300;
  letter-spacing: 0.01em;
  line-height: 0.98;
  text-transform: uppercase;
  overflow-wrap: anywhere;
}
/* the lead statement: big, tight, the first line indented with an arrow before it (pxpush's .lead__indent) */
.event-description {
  position: relative;
  margin: 0 0 clamp(40px, 5vw, 96px);
  color: var(--pf-fg);
  font-family: var(--hud-display);
  font-size: clamp(1.1rem, 2.2vw, 3.1rem);
  font-weight: 300;
  line-height: 1.2;
  text-indent: clamp(0px, 12vw, 280px);
}
.event-description::before {
  content: "\2192";
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0.5;
  text-indent: 0;
}

/* pictures: a flat tint fill, a thin frame in the text colour */
.single-event img:not(.pf-gal__img),
.discord-event__pic > img {
  display: block;
  border: 1px solid var(--pf-fg);
  background: var(--pf-bg);
  color: var(--pf-fg);
  object-fit: cover;
}

/* the small date line under a cover (a square bullet and mono caps) */
.event-card-date {
  display: flex;
  align-items: center;
  gap: 8px;
  color: inherit;
  font-family: var(--hud-mono);
  font-size: clamp(0.72rem, 0.85vw, 1rem);
  letter-spacing: -0.05vw;
  line-height: 1.5;
  text-transform: uppercase;
}
.event-card-date::before {
  content: "";
  flex: none;
  width: 0.6em;
  height: 0.6em;
  background: currentColor;
}

/* ---------- single events + discord events: plain blocks under a thin rule (drawn left to right by the scroll: --sep) ---------- */

.single-event,
.discord-event {
  position: relative;
  margin-top: clamp(40px, 5vw, 96px);
  padding-top: clamp(24px, 2.4vw, 40px);
  color: var(--pf-fg);
  scroll-margin-top: 24px;
}
.single-event::before,
.discord-event::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  height: 1px;
  background: var(--pf-soft);
  transform: scaleX(var(--sep, 1));
  transform-origin: 0 50%;
}
.single-event h3,
.discord-event h3 {
  margin-bottom: clamp(16px, 1.6vw, 28px);
  color: var(--pf-fg);
  font-family: var(--hud-display);
  font-size: clamp(1.2rem, 2.5vw, 3.4rem);
  font-weight: 300;
  letter-spacing: 0.02em;
  line-height: 1.1;
  text-transform: uppercase;
}
.single-event p,
.discord-event p {
  max-width: 46rem;
  margin: 0 0 1em;
  color: var(--pf-fg);
  font-family: var(--hud-mono);
  font-size: clamp(0.82rem, 1.19vw, 1.2rem);
  letter-spacing: -0.02em;
  line-height: 1.55;
}
.single-event p a {
  color: var(--pf-fg);
  font-weight: 700;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
.single-event p a:hover {
  background: var(--pf-fg);
  color: var(--pf-bg);
}
.single-event img:not(.pf-gal__img) {
  width: 100%;
  max-width: 760px;
  height: auto;
  margin: 0 0 clamp(20px, 2vw, 36px);
}

/* the three discord events on one level (events.js: the triggered sequence [data-seq]): equal columns from the left page gutter to the
   right one, top-aligned; each column is a subgrid of the row's three rows (title / text / picture), so the pictures line up
   whatever the length of the texts. Under 1000px they stack in page order. */
.discord-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: auto auto auto;
  column-gap: clamp(24px, 3vw, 56px);
  align-items: start;
  margin-top: clamp(40px, 5vw, 96px);
}
.discord-row > .discord-event {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  margin-top: 0;
}
.discord-event__text p:last-child {
  margin-bottom: 0;
}
.discord-event__pic {
  min-width: 0;
  margin-top: clamp(16px, 2vw, 32px);
}
.discord-event__pic > img {
  width: 100%;
  height: auto;
}
/* the reveal: hidden only while the script is up and motion is allowed (.pf-anim); events.js adds .is-in column by column */
.pf-anim .discord-event[data-seq-item] {
  opacity: 0;
  transform: translateY(32px);
}
.pf-anim .discord-event[data-seq-item].is-in {
  opacity: 1;
  transform: none;
  transition: opacity 0.6s ease, transform 0.8s cubic-bezier(0.22, 0.61, 0.36, 1);
}
@media (max-width: 999px) {
  .discord-row {
    display: block;
  }
  .discord-row > .discord-event {
    display: block;
    margin-top: clamp(40px, 5vw, 96px);
  }
  .discord-row > .discord-event:first-child {
    margin-top: 0;
  }
  .discord-event__pic > img {
    max-width: 420px;
  }
}

/* ---------- the link buttons (the pxpush bottom buttons) ----------
   A full-width cell of the column: a thin rule on top (drawn by the scroll), the mono label at the left, the arrow at
   the right. On hover (and focus) a fill in the text colour rises from the bottom (0.2s), the label and the arrow turn
   to the section colour and move in by a few px; on leave the fill retracts upward (0.1s). events.js adds the quick
   letter scramble of the label on hover. */

.button-group {
  margin: clamp(24px, 2.4vw, 44px) 0 0;
  border-bottom: 1px solid var(--pf-fg);
}
.pf-btn {
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  width: 100%;
  min-height: 44px;
  padding: clamp(10px, 1vw, 18px) 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--pf-fg);
  font-family: var(--hud-mono);
  font-size: clamp(0.8rem, 1.19vw, 1.2rem);
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1.3;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: color 0.12s ease;
}
/* the rule on top of the cell: its own layer, so the scroll can draw it from the left */
.pf-btn::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  z-index: 1;
  height: 1px;
  background: var(--pf-fg);
  transform: scaleX(var(--sep, 1));
  transform-origin: 0 50%;
}
.pf-btn::before {
  content: "";
  position: absolute;
  inset: 0 0 -1px;
  z-index: -1;
  background: var(--pf-fg);
  transform: scaleY(0);
  transform-origin: 50% 0;
  transition: transform 0.1s ease-in-out;
}
.pf-btn:hover,
.pf-btn:focus-visible {
  color: var(--pf-bg);
}
.pf-btn:hover::before,
.pf-btn:focus-visible::before {
  transform: scaleY(1);
  transform-origin: 50% 100%;
  transition: transform 0.2s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.pf-btn__label,
.pf-btn__arrow {
  position: relative;
  transition: padding 0.3s ease;
}
.pf-btn:hover .pf-btn__label,
.pf-btn:focus-visible .pf-btn__label {
  padding-left: 12px;
}
.pf-btn:hover .pf-btn__arrow,
.pf-btn:focus-visible .pf-btn__arrow {
  padding-right: 12px;
}
.pf-btn__arrow {
  flex: none;
  min-width: 1ch;
  text-align: right;
}
/* the arrow is drawn here, not in the markup (it is decoration; the label is the name) */
.pf-btn__arrow::before {
  content: "\2197\FE0E";
}
.external-link-disabled .pf-btn__arrow::before {
  content: none;
}
.pf-btn:focus-visible {
  outline-offset: -2px;
}

/* the toggle of an expander: a plus that turns into a minus */
.pf-toggle .pf-btn__arrow::before {
  content: "+";
}
.pf-toggle[aria-expanded="true"] .pf-btn__arrow::before {
  content: "\2212";
}

/* the charity action: a label inside the button with a square bullet */
.external-link-charity {
  flex-wrap: wrap;
}
.charity-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
  padding: 0 8px;
  background: var(--pf-fg);
  color: var(--pf-bg);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  line-height: 1.8;
  text-transform: uppercase;
  transition: background-color 0.12s ease, color 0.12s ease;
}
.charity-badge::before {
  content: "";
  flex: none;
  width: 6px;
  height: 6px;
  background: currentColor;
}
.pf-btn:hover .charity-badge,
.pf-btn:focus-visible .charity-badge {
  background: var(--pf-bg);
  color: var(--pf-fg);
}

/* a lone button closing a block (physical drop) */
.single-event > .pf-btn {
  margin: clamp(24px, 2.4vw, 44px) 0 0;
  border-bottom: 1px solid var(--pf-fg);
}

/* disabled: dashed, quiet, not-allowed; hover / active repeated so they cannot undo it */
.external-link-disabled,
.external-link-disabled:hover,
.external-link-disabled:focus-visible,
.external-link-disabled:active {
  border-top: 1px dashed var(--pf-soft);
  background: none;
  color: var(--pf-soft);
  cursor: not-allowed;
}
.external-link-disabled::before,
.external-link-disabled::after {
  display: none;
}
.external-link-disabled:hover .pf-btn__label,
.external-link-disabled:focus-visible .pf-btn__label,
.external-link-disabled:hover .pf-btn__arrow {
  padding: 0;
}


/* ---------- the sticker model stage (portfolio-stickers.js) ----------
   pxpush's pricing section keeps its packaging model on a fixed canvas on the left that fades in and out with the section while the text
   scrolls past; here the stage is sticky, a screen high, the left part of the section (left of the text column), its top at the top of
   the section (it overlaps the stripe, as pxpush's model does) and it takes no room in the flow. The script fades it by the scroll (opacity,
   a little blur and scale: inline styles) once the section's content has been reached and again before the section ends, so the
   section is given a long tail of scroll (1.1 screens of padding, the last of them under the colour band) in which the model stays pinned
   next to the text. Without the script (no JS, no WebGL, no import map) the picture of the sticker sheet inside stays; with it
   (.is-live) the canvas replaces it. The canvas takes the pointer (drag to turn the model, as the home page's model), a vertical swipe
   still scrolls the page (touch-action: pan-y). */

.pf-stage {
  position: sticky;
  top: 0;
  z-index: 1;
  width: calc(40% - var(--yh-g));
  height: 100vh;
  margin-top: calc(-1 * var(--pf-stripe-h));
  margin-bottom: calc(-100vh + var(--pf-stripe-h));
  pointer-events: none;
}
.pf-stage__still {
  position: absolute;
  top: 50%;
  left: 50%;
  display: block;
  width: auto;
  height: min(72vh, 680px);
  transform: translate(-50%, -50%);
}
.pf-stage.is-live {
  opacity: 0;
}
.pf-stage.is-live .pf-stage__still {
  display: none;
}
.pf-stage__gl {
  position: absolute;
  inset: 0;
}
.pf-stage__cv {
  display: block;
  width: 100%;
  height: 100%;
  cursor: grab;
  pointer-events: auto;
  touch-action: pan-y;
}
.pf-stage.is-dragging .pf-stage__cv {
  cursor: grabbing;
}
/* the hint (as on the home page's model): gone after the first touch */
.pf-stage__hint {
  position: absolute;
  bottom: clamp(48px, 9vh, 96px);
  left: 50%;
  display: none;
  padding: 4px 10px;
  border: 1px solid var(--pf-soft);
  color: var(--pf-soft);
  font-family: var(--hud-mono);
  font-size: clamp(0.68rem, 0.8vw, 0.9rem);
  letter-spacing: 0.08em;
  line-height: 1.3;
  text-transform: uppercase;
  white-space: nowrap;
  transform: translateX(-50%);
}
.pf-stage.is-live .pf-stage__hint {
  display: block;
}
.pf-stage.is-touched .pf-stage__hint {
  display: none;
}
/* the long tail of the section (the model stays pinned while the text scrolls past, then there is a screen more to look at it before
   the next colour band rises): only where the model is pinned and motion is allowed */
@media (min-width: 720px) and (prefers-reduced-motion: no-preference) {
  #physical {
    --pf-end-pad: 110vh;
  }
}

/* ---------- the genre-roulette card pack (a still picture) ----------
   Four transparent cards lying loosely on each other: a little rotated and offset, each with a soft shadow that follows the card's own
   outline, so the transparent corners stay transparent. data-p is the card's place in the pile (0 = the front; --p is its number). It
   does nothing: no hover, no focus, no script (the owner dropped the fan-out and the swapping of the front card). */

.pf-pack {
  --pack-h: clamp(300px, 26vw, 460px);
  position: relative;
  width: 100%;
  height: var(--pack-h);
  margin: clamp(16px, 2vw, 32px) 0 0;
}
.pf-pack__card {
  --jit: 0deg;
  position: absolute;
  top: 6%;
  left: 50%;
  z-index: calc(10 - var(--p, 0));
  width: auto;
  height: 86%;
  max-width: none;
  transform-origin: 50% 100%;
  transform:
    translate(-50%, 0)
    translate(calc((var(--p, 0) - 1.5) * 22px), calc(var(--p, 0) * -6px))
    rotate(calc((var(--p, 0) - 1.5) * 4deg + var(--jit)));
  filter: drop-shadow(0 6px 9px rgba(30, 48, 73, 0.4));
  pointer-events: none;
  -webkit-user-drag: none;
  user-select: none;
}
.pf-pack__card[data-p="0"] { --p: 0; --jit: -1.5deg; }
.pf-pack__card[data-p="1"] { --p: 1; --jit: 2deg; }
.pf-pack__card[data-p="2"] { --p: 2; --jit: -3deg; }
.pf-pack__card[data-p="3"] { --p: 3; --jit: 1deg; }

/* ---------- the LORE panel: an old Windows window (Win9x / XP) ----------
   A bevelled light-grey face (white top-left edge, dark bottom-right edge, like a classic raised window) with a light-blue
   title strip, a small caption and three bevelled caption buttons. The strip is decoration (aria-hidden); the text inside is the
   ordinary .single-event. Only palette-safe greys and light blues (hue ~210). */

.pf-win {
  margin-top: clamp(8px, 1vw, 16px);
  padding: 3px;
  border: 1px solid #5d6a79;
  background: #dfe5ec;
  box-shadow: inset 1px 1px 0 #fff, inset -1px -1px 0 #8493a4, 3px 3px 0 rgba(30, 48, 73, 0.35);
  color: var(--yh-ink);
}
.pf-win__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 3px 4px 3px 10px;
  background: linear-gradient(90deg, #8fb7e6 0%, #bcd6f1 55%, #d6e6f7 100%);
  color: var(--yh-ink);
  font-family: var(--hud-mono);
  font-size: clamp(0.72rem, 0.85vw, 1rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  line-height: 1.3;
}
.pf-win__btns {
  display: inline-flex;
  gap: 2px;
}
.pf-win__btns i {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5em;
  height: 1.3em;
  background: #dfe5ec;
  box-shadow: inset 1px 1px 0 #fff, inset -1px -1px 0 #5d6a79;
  font-size: 0.85em;
  font-style: normal;
  line-height: 1;
}
.pf-win .single-event {
  margin-top: 0;
  padding: clamp(16px, 2vw, 32px);
}
.pf-win .single-event::before {
  display: none;
}
.pf-win .single-event p {
  max-width: none;
}

/* the second window of the LORE section: madoka.gif beside the text (the left 40%, under the section label), the same face and title
   strip as lore.txt around a sunken white viewer. events.js lets it drift up / down a little with the scroll ([data-parallax]); the
   transform is its only motion, so it lies in the flow like any block (and above the text on a narrow screen). */
.pf-win--pic {
  max-width: min(100%, 560px);
  margin: 0;
}
.pf-win__pic {
  padding: 2px;
  background: #fff;
  box-shadow: inset 1px 1px 0 #8493a4, inset -1px -1px 0 #fff;
}
.pf-win__pic img {
  display: block;
  width: 100%;
  height: auto;
}
/* the collage of the LORE media column: the madoka window in the flow, and screencore.png in a second window of the same frame (.pf-win,
   .pf-win--pic: grey face, light-blue title strip, sunken white client area) under it at the RIGHT edge of the column, pulled up a
   little over the madoka window's lower-right corner and standing above it (z-index) on a layer of its own (events.js drifts it the
   other way at 0.6 of the window's rate, [data-parallax="-0.6"]). Both are in the flow of one grid, so the column is as tall as the
   pair and nothing needs a guessed padding. The owner nudges the second window with these variables (percentages are of the column's
   width, so the pair keeps its shape at any size):
     --sc-w     width of the window
     --sc-up    how far it climbs up over the madoka window's bottom edge
     --sc-out   how far it goes out past the column's right edge, into the page margin the column leaves (0 below 1000px, where the
                column is the whole page width)
     --sc-tilt  a slight turn (the rotate property: it does not touch the transform the script drifts it with); 0 = straight */
.pf-collage {
  --sc-w: 46%;
  --sc-up: 3%;
  --sc-out: clamp(16px, 2.4vw, 48px);   /* = the column's own right padding: the window ends at the page margin, never outside it */
  --sc-tilt: 0deg;
  display: grid;
  width: 100%;
}
.pf-collage > .pf-win--pic:first-child {
  width: 100%;
}
.pf-shot {
  position: relative;
  z-index: 2;
  justify-self: end;
  max-width: none;
  width: var(--sc-w);
  margin: calc(-1 * var(--sc-up)) calc(-1 * var(--sc-out)) 0 0;
  rotate: var(--sc-tilt);
}
@media (max-width: 999px) {
  .pf-collage { --sc-out: 0px; --sc-w: 52%; }
}
.pf-split--lore .pf-split__media {
  flex: 1 1 0;
  margin-left: 0;
  padding-top: calc(var(--pf-wrap-pt) + 2.6rem + 70px);   /* + the drift (DRIFT_PX in events.js): at its highest the window still clears the label */
  padding-right: clamp(16px, 2.4vw, 48px);
}
.pf-split--lore .pf-wrap {
  flex: 0 0 auto;
  width: min(100%, max(36rem, calc(60vw - var(--yh-g))));
  margin-left: auto;
}
@media (max-width: 999px) {
  .pf-split.pf-split--lore { display: block; }
  .pf-split--lore .pf-split__media { margin: 0; padding: calc(var(--pf-wrap-pt) + 2.6rem + 40px) 0 40px; }   /* room for the smaller drift (DRIFT_STACKED) both ways */
  .pf-split--lore .pf-wrap { width: min(100%, 36rem); margin-left: auto; }
}

/* ---------- inline expanders (they replace the pop-ups) ----------
   The panel is a one-row grid whose row grows from 0fr to 1fr (a height slide); closed, it is invisible and
   out of the tab order (visibility). events.js reveals the rows one after the other. Without JS the page
   prints the lists open (the <noscript> style in events.ejs). */

.pf-more {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.4s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.pf-more.is-open {
  grid-template-rows: 1fr;
}
.pf-more__clip {
  min-height: 0;
  overflow: hidden;
  visibility: hidden;
  transition: visibility 0s linear 0.4s;
}
.pf-more.is-open .pf-more__clip {
  visibility: visible;
  transition-delay: 0s;
}
.pf-more__title {
  margin: 0;
  padding: 20px 0 8px;
  border-top: 1px solid var(--pf-fg);
  color: var(--pf-soft);
  font-family: var(--hud-mono);
  font-size: 0.7rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  line-height: 1.4;
  text-transform: uppercase;
}
.pf-more__sub::before {
  content: "/";
  margin-right: 8px;
}
.artist-list {
  display: grid;
  gap: 0;
  padding: 0 0 20px;
  list-style: none;
}
.artist-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: flex-start;
  gap: 0 0.45em;
  padding: 8px 0;
  border-top: 1px solid var(--pf-soft);
  color: var(--pf-fg);
  font-size: 0.98rem;
  line-height: 1.5;
  text-align: left;
}
.pf-anim .pf-more li {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 0.2s ease, transform 0.25s cubic-bezier(0.22, 0.61, 0.36, 1);
}
.pf-anim .pf-more li.is-in {
  opacity: 1;
  transform: none;
}
.artist-list a {
  color: var(--pf-fg);
  font-weight: 700;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
.artist-list a:hover {
  background: var(--pf-fg);
  color: var(--pf-bg);
}
/* the stream-schedule times: small mono labels at the start of the row */
.artist-list .time {
  display: inline-block;
  min-width: 4.8em;
  margin-right: 0.8em;
  padding: 0 8px;
  border: 1px solid var(--pf-fg);
  color: var(--pf-fg);
  font-family: var(--hud-mono);
  font-size: 0.78rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* ---------- the section change: ten bands in the next colour (events.js drives their height with the scroll) ----------
   Exactly pxpush's .overlay: absolute at the bottom of the leaving section, a full screen high, above the section's
   own content (z-index 4); its ten children are the bands, each scaling up from the bottom (power4.out, stagger 0.04)
   while the overlay's top travels from the top of the screen (0%) to 80vh above it. */

.pf-wipe {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 4;
  display: grid;
  grid-template-rows: repeat(10, 1fr);
  height: var(--pf-wipe-h);
  pointer-events: none;
}
.pf-wipe i {
  background: var(--pf-next);
  transform: scaleY(0);
  transform-origin: 50% 100%;
  will-change: transform;
}
.pf-wipe[data-to="yellow"] { --pf-next: var(--pf-bg-yellow); }
.pf-wipe[data-to="white"] { --pf-next: var(--pf-bg-white); }
.pf-wipe[data-to="blurple"] { --pf-next: var(--pf-bg-blurple); }
.pf-wipe[data-to="dark"] { --pf-next: var(--pf-bg-dark); }
.pf-wipe[data-to="lore"] { --pf-next: var(--pf-bg-lore); }

/* ---------- the dotted screen (portfolio-dots.js) ----------
   One fixed canvas over the page (inside .pf-main, below the fixed index and the header): a halftone / noise dot texture
   over the whole screen with a torn cloud around the mouse that clears the dots. It lays its dots over whatever is
   behind it as a translucent tint (a per-row colour + alpha worked out from the section's --pf-dots over its --pf-bg, so a dot
   is exactly the dot tone on the colour that is really there, also while the colour bands rise; darker than the ground on
   the light sections, LIGHTER on the dark one: a plain blend, not a multiply), above the bands and the text: the dot tones
   keep text readable (tests/portfolio-effects.test.js). It is aria-hidden and ignores the pointer; the script draws nothing
   without a section behind a row, a still pattern under reduced motion and on touch. */

.pf-dots {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 5;
  width: 100%;
  height: 100%;
  pointer-events: none;
  image-rendering: pixelated;
}

/* ---------- the top exit (pxpush's .topOverlay + [effect__fadeOut]) ----------
   Content that scrolls up under the top of the screen ghosts out: a strip 20vh high, sticky at the top of its section,
   in that section's colour (fully opaque at the very top, transparent at the bottom edge: pxpush's
   linear-gradient(180deg, bg 2%, transparent 100%)) with a soft blur of what lies behind it. It sits above the content and
   below the colour bands (z-index 2 against 4), it fades in once its section's top has left the screen (events.js adds
   .is-on; pxpush: .topOverlay__section.is-visible, opacity .3s), and the fixed index and the header are not behind it.
   The stripes alone also blur out by the scroll (events.js, pxpush's marquee: opacity 1 -> 0 and blur 0 -> 20px from
   "top 5%" to "top -30%"). Static CSS: nothing here moves, so reduced motion keeps the gradient and only loses the fade-in. */

.pf-topfade {
  position: sticky;
  top: 0;
  z-index: 2;
  height: 20vh;
  margin-bottom: -20vh;
  background: linear-gradient(180deg, var(--pf-bg) 2%, transparent 100%);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}
.pf-topfade.is-on {
  opacity: 1;
}
.pf-topfade::before {
  content: "";
  position: absolute;
  inset: 0;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  -webkit-mask-image: linear-gradient(180deg, black 0%, transparent 100%);
  mask-image: linear-gradient(180deg, black 0%, transparent 100%);
}

/* ---------- SUNCYCLE 3 (the first section: the current event) ----------
   A 16:9 stage of nine transparent layers, bottom to top: BG FULL (z 1), BLUE BG ELEMENTS (2), [the dotted screen of the page: .pf-dots,
   z 5, inside .pf-main], ARROW + GREENTXT + CYIII WHITE (6), TANYA + 3 FULL (7), SC TEXT + TRIA (8). The dots can only lie between the
   second and third layer because the stage is NOT a stacking context (no transform / opacity / contain / container-type on it or on
   .sc3): its layers and the dots canvas share the stacking context of .pf-main. All nine are there at once, no fade; events.js moves
   them down by scrollY * data-sc3-rate (the far layers most), so they trail the page a little (none under reduced motion).
   The text sits in the empty lower right of the picture (the owner's red box: left 61.6%, top 48.6%, width 36.4% of the stage) and runs
   on below it. Sizes are % (geometry) and vw (type) because container units would make a stacking context. The section is a screen
   taller than the stage, so the colour wipe (the last 100vh, z 4) starts only once the stage has left the screen and the layers
   above the dots never lie over the rising bands. No top fade here (it would lie over the dots and under the upper layers). */

.event-section--sc3 {
  padding: 0;
}
.sc3 {
  position: relative;
  min-height: calc(56.25vw + 130vh);   /* a screen taller than the picture, plus 30vh so the lower part (the button) sits a while before the bands rise */
}
.sc3__art {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
}
.sc3__layer {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  user-select: none;
  pointer-events: none;
  will-change: transform;
}
/* the two layers that touch the edges of the picture are enlarged about their bottom middle, so that the lag of the scroll (up to 6% of the
   scroll) and the lean of the cursor never show the white of the page at the top and the sides (7.5% = 80px of height, 71px each side) */
.sc3__layer--bg { z-index: 1; scale: 1.075; transform-origin: 50% 100%; }
.sc3__layer--blue { z-index: 2; scale: 1.06; transform-origin: 50% 100%; }
.sc3__layer--arrow,
.sc3__layer--green,
.sc3__layer--cyiii { z-index: 6; }
.sc3__layer--tanya,
.sc3__layer--three { z-index: 7; }
.sc3__layer--title,
.sc3__layer--tria { z-index: 8; }

/* the text: its top is 48.6% of the stage (56.25% x 48.6% = 27.3% of the width) from the top of the stage, so it is pulled up by the rest */
.sc3__text {
  position: relative;
  z-index: 3;
  width: 36.4%;
  margin: -28.9% 0 0 61.6%;
  color: var(--pf-fg);
  font-family: var(--hud-display);
  font-size: clamp(0.95rem, 1.2vw, 1.75rem);   /* the dots lie over the text: small type is eaten by them, so it is as big as the other sections' */
  font-weight: 500;
  line-height: 1.32;
}
.sc3__text p {
  margin: 0 0 1.15em;
}
/* the lower part of the text starts further left (the owner's red line, 46.5% of the width): -41.5% of this column = 15.1% of the page,
   the right edge stays where it was */
.sc3__wide {
  margin-left: -41.5%;
}
/* the middle part («24ex ЧАСОВОЕ…» and the blue line) starts at the owner's orange line (55.8% of the width: 15.9% of the column to the left of it)
   and is a step bigger */
.sc3__mid {
  margin-left: -15.9%;
  font-size: 1.12em;
}
/* «отправить заявку»: the home page's call-to-action cell as a compact plate, under the text (the width of the lower part) */
.sc3__cta {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  gap: clamp(12px, 1.6vw, 28px);
  min-height: clamp(180px, 15.5vw, 300px);
  margin: clamp(32px, 3.2vw, 64px) 0 0;
  padding: clamp(14px, 1.2vw, 22px) clamp(16px, 1.6vw, 30px) clamp(16px, 1.4vw, 26px);
  overflow: hidden;
  background: var(--yh-accent);
  color: var(--yh-on-accent);
  font-family: var(--hud-mono);
  text-decoration: none;
  transition: color 0.12s ease;
}
.sc3__cta::before {
  content: "";
  position: absolute;
  inset: 0 0 -1px;
  z-index: -1;
  background: var(--yh-accent-deep);
  transform: scaleY(0);
  transform-origin: 50% 0;
  transition: transform 0.1s ease-in-out;
}
.sc3__cta::after {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  height: 2px;
  background: var(--yh-ink);
}
.sc3__cta:hover::before,
.sc3__cta:focus-visible::before {
  transform: scaleY(1);
  transform-origin: 50% 100%;
  transition: transform 0.2s cubic-bezier(0.22, 0.61, 0.36, 1);
}
/* the oil-film canvas of home-glow.js (the gasoline colours round the pointer): inside the plate, over the fill and under the text; the status row
   at the top and the line at the foot stay clear of it (their small type needs the plain blue), as on the home page */
.sc3__cta .hc-film {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  image-rendering: pixelated;
  -webkit-mask-image: linear-gradient(180deg, transparent 0, transparent 38px, #000 74px, #000 calc(100% - 74px), transparent calc(100% - 38px));
  mask-image: linear-gradient(180deg, transparent 0, transparent 38px, #000 74px, #000 calc(100% - 74px), transparent calc(100% - 38px));
}
.sc3__cta-meta,
.sc3__cta-label,
.sc3__cta-sub {
  position: relative;
  z-index: 1;
}
.sc3__cta-meta {
  display: flex;
  align-items: center;
  gap: 0.9em;
  font-size: clamp(11px, 0.8vw, 14px);
  font-weight: 500;
  letter-spacing: 0.04em;
  line-height: 1.35;
}
.sc3__cta-index { flex: none; font-weight: 700; }
.sc3__cta-rule { flex: 1 1 14px; height: 1px; min-width: 14px; background: currentColor; opacity: 0.5; }
.sc3__cta-status { display: flex; align-items: center; gap: 0.7em; }
.sc3__cta-led { flex: none; width: 0.62em; height: 0.62em; background: currentColor; }
.sc3__cta-arrow { flex: none; min-width: 1ch; font-size: 1.5em; line-height: 1; text-align: right; }
.sc3__cta-arrow::before { content: "\2197\FE0E"; display: inline-block; }
.sc3__cta-label {
  margin-top: auto;
  font-family: var(--hud-display);
  font-size: clamp(2rem, 4.6vw, 6.5rem);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 0.92;
  text-transform: uppercase;
  white-space: nowrap;
}
.sc3__cta-sub {
  font-size: clamp(11px, 0.8vw, 14px);
  font-weight: 500;
  letter-spacing: 0.04em;
  line-height: 1.4;
}
.sc3__when {
  display: flex;
  align-items: center;
  gap: 1.1vw;
  margin: 0 0 1.4em;
  color: var(--pf-hdr-blue);
}
.sc3__sign {
  flex: none;
  width: clamp(64px, 7.4vw, 150px);
  height: auto;
}
.sc3__text .sc3__dates {
  display: flex;
  flex-direction: column;
  margin: 0;
  font-size: clamp(1.3rem, 2.5vw, 3.6rem);
  font-weight: 800;
  line-height: 1.05;
  letter-spacing: -0.01em;
}
.sc3__soft {
  color: var(--pf-soft);
}
.sc3__strong {
  font-weight: 800;
}
.sc3__blue {
  margin-right: 0;
  color: var(--pf-hdr-blue);
  font-size: 1.6em;
  font-weight: 500;
  line-height: 1.15;
  text-align: right;
}
.sc3__tag {
  font-family: var(--hud-mono);
  font-weight: 800;
  letter-spacing: 0.22em;
  white-space: nowrap;
}
.sc3__tag sup {
  font-size: 0.6em;
  letter-spacing: 0;
}

/* the glints on the title: a soft white sweep, cut to the shape of the SC TEXT layer (its alpha is the mask), over it (z 8, after it in
   the markup). It rests most of the time: 1.3 s of sweep in a 5.5 s cycle. Not under reduced motion. */
.sc3__shine {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 8;
  width: 100%;
  height: 100%;
  background: linear-gradient(105deg, transparent 42%, rgba(255, 255, 255, 0.9) 50%, transparent 58%) no-repeat;
  background-size: 300% 100%;
  background-position: 120% 0;
  -webkit-mask: url("/images/events/sc3/sctext.webp") center / 100% 100% no-repeat;
  mask: url("/images/events/sc3/sctext.webp") center / 100% 100% no-repeat;
  pointer-events: none;
  animation: sc3-glint 5.5s ease-in-out infinite;
}
@keyframes sc3-glint {
  0% { background-position: 120% 0; }
  24%, 100% { background-position: -20% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .sc3__shine { display: none; }
}

@media (max-width: 899px) {
  /* a stacked fallback until the phone layout is designed: the picture, then the text under it */
  .sc3 {
    min-height: 0;
    padding-bottom: 40vh;
  }
  .sc3__text {
    width: auto;
    margin: 0;
    padding: 28px var(--yh-g) 0;
    font-size: 0.95rem;
  }
  .sc3__wide,
  .sc3__mid {
    margin-left: 0;
  }
  .sc3__mid {
    font-size: 1em;
  }
  .sc3__cta-label {
    font-size: clamp(1.6rem, 9vw, 3rem);
  }
  .sc3__when {
    gap: 14px;
  }
  .sc3__blue {
    font-size: 1.3em;
  }
}

/* ---------- gradual text (events.js: scroll-scrubbed like pxpush; present only when motion is allowed, otherwise everything is simply there) ----------
   Words are wrapped in .pf-wd; events.js writes their opacity (and, for the "random" kind, a small push in depth)
   from the scroll position. Inline until it does; a word of the "random" kind is an inline block so it can move. */

.pf-wd--r {
  display: inline-block;
  transform-origin: 50% 100%;
}


/* ---------- the Bleeding Hearts Building section: the gallery on the left half, the text on the right half ----------
   .pf-split holds both; the gallery column is sticky (the text scrolls past it, the pictures keep cycling) and runs from the left
   edge of the page to the middle of it, the text column takes the other half and keeps the reveals of the other sections. On a
   phone they stack (gallery above, text below). */

.pf-split {
  display: flex;
  align-items: stretch;
}
.pf-split__media {
  flex: 0 0 calc(50% + var(--yh-g));
  min-width: 0;
  margin-left: calc(-1 * var(--pf-pl));
  padding-top: calc(var(--pf-wrap-pt) + 2.6rem);
}
.pf-split .pf-wrap {
  flex: 1 1 0;
  width: auto;
  min-width: 0;
  margin-left: clamp(24px, 3vw, 56px);
}

/* ---------- the cover above the gallery (portfolio-cover.js) ----------
   A plain picture in the left column. With the script it is drawn by a WebGL plane that bends while the page is scrolled (the
   mechanism of k95.it's "distort canvas", docs/bhb-cover.md) and lies flat again when the scrolling stops; the canvas is the picture's
   size plus a 48px margin, because the bend overflows the box. Without the script (no JS, reduced motion, touch, no WebGL) nothing
   here moves. The <img> keeps its layout and only turns invisible once the plane is up (.is-gl). */

.pf-cover {
  --pf-cover-pad: 48px;
  position: relative;
  width: min(calc(100% - var(--yh-g) - clamp(16px, 2vw, 32px)), 680px);
  margin: 0 0 clamp(20px, 3vw, 48px) var(--yh-g);
}
.pf-cover__img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;   /* the picture is square and its title sits near the top edge: no crop at this width */
  object-fit: cover;
}
.pf-cover.is-gl .pf-cover__img {
  opacity: 0;
}
.pf-cover__gl {
  position: absolute;
  top: calc(-1 * var(--pf-cover-pad));
  left: calc(-1 * var(--pf-cover-pad));
  z-index: 1;
  width: calc(100% + 2 * var(--pf-cover-pad));
  height: calc(100% + 2 * var(--pf-cover-pad));
  pointer-events: none;
}

/* the two Suncycle covers (the same module and rules as the Bleeding Hearts cover): stacked in the left column, each a link to its
   block on the right (#sc2, #sc1), with a small caption under it (the title and the date, as on the old cards) */
.pf-cover--vol {
  width: min(calc(100% - var(--yh-g) - clamp(16px, 2vw, 32px)), 560px);
  margin-bottom: clamp(32px, 4vw, 64px);
}
.pf-cover__frame {
  position: relative;
}
.pf-cover__link {
  display: block;
  color: inherit;
}
.pf-cover__link:focus-visible {
  outline-offset: 4px;
}
.pf-cover__cap {
  margin-top: clamp(10px, 1vw, 16px);
}
.pf-cover__cap h3 {
  margin: 0 0 6px;
  color: var(--pf-fg);
  font-family: var(--hud-display);
  font-size: clamp(0.95rem, 1.25vw, 1.5rem);
  font-weight: 300;
  letter-spacing: 0.04em;
  line-height: 1.3;
  text-transform: uppercase;
}

/* ---------- the gallery (portfolio-gallery.js) ----------
   Without the script the slot is a plain grid of framed thumbnails, each a link to the full picture. With it (.pf-gal--live) the
   slot becomes the stage of unbelievablycrap.com/gallery's field: a camera (the stage's perspective) rides a closed curve, the
   planes sit along it, so pictures come out of the distance, travel toward the viewer and leave, for ever; near ones in full colour,
   far ones fading and greying. Every picture sits in a thin ink frame with a mat of the section's own colour. A click opens the
   lightbox. The ten links stay real (hidden while the field is on; a focused one shows its thumbnail in the corner). */

.pf-gallery-slot {
  position: relative;
  margin: 0;
}
.pf-gal__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(190px, 44%), 1fr));
  gap: clamp(12px, 1.6vw, 24px);
  margin: 0;
  padding: 0;
  list-style: none;
}
.pf-gal__item {
  margin: 0;
}
.pf-gal__link {
  display: block;
  padding: 4px;
  border: 1px solid var(--pf-fg);
  background: var(--pf-bg);
  color: var(--pf-fg);
  text-decoration: none;
  transition: background-color 0.15s ease;
}
.pf-gal__img {
  display: block;
  width: 100%;
  height: auto;
  margin: 0;
  border: 0;
  -webkit-user-drag: none;
  user-select: none;
}
.pf-gal__hint {
  margin: 12px 0 0;
  color: var(--pf-fg);
  font-family: var(--hud-mono);
  font-size: clamp(0.72rem, 0.85vw, 1rem);
  letter-spacing: -0.02em;
  line-height: 1.3;
  text-transform: uppercase;
}

/* the live stage: sticky on the left, about a screen high, clipped (not a scroll container: focus cannot scroll it) */
.pf-gallery-slot.pf-gal--live {
  position: sticky;
  top: 0;
  height: clamp(480px, 100vh, 1000px);
  overflow: hidden;
  overflow: clip;
  border-right: 1px solid var(--pf-soft);
  perspective: 525px;
  perspective-origin: 50% 50%;
}
/* the ten real links: out of sight while the field shows the pictures (they are still focusable and named) */
.pf-gal--live .pf-gal__list {
  position: absolute;
  inset: 0;
  z-index: 3;
  display: block;
  pointer-events: none;
}
.pf-gal--live .pf-gal__item:not(:has(.pf-gal__link:focus-visible)) {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
}
.pf-gal--live .pf-gal__item:has(.pf-gal__link:focus-visible) {
  position: absolute;
  bottom: 56px;
  left: var(--yh-g);
  width: clamp(110px, 12vw, 180px);
  pointer-events: auto;
}
.pf-gal--live .pf-gal__link:focus-visible {
  background: var(--pf-fg);
  outline: 2px solid var(--pf-fg);
  outline-offset: 4px;
}
/* the camera's world: a point at the centre of the stage, the view matrix on it, the planes in it */
.pf-gal__world {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  transform-style: preserve-3d;
  will-change: transform;
  pointer-events: none;
}
.pf-gal__plane {
  position: absolute;
  top: 0;
  left: 0;
  box-sizing: content-box;
  padding: 4px;
  border: 1px solid var(--pf-fg);
  background: var(--pf-bg);
  transform-style: preserve-3d;
  backface-visibility: visible;
  pointer-events: auto;
  cursor: zoom-in;
}
.pf-gal__plane.is-on {
  will-change: transform, opacity;
}
.pf-gal__crop {
  overflow: hidden;
}
.pf-gal__pic {
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
  transform-origin: 50% 50%;
  -webkit-user-drag: none;
  user-select: none;
}
.pf-gal--live .pf-gal__hint {
  position: absolute;
  bottom: 12px;
  left: var(--yh-g);
  z-index: 2;
  margin: 0;
  padding: 4px 8px;
  border: 1px solid var(--pf-fg);
  background: var(--pf-bg);
  pointer-events: none;
}

/* ---------- the lightbox (built by portfolio-gallery.js, a child of <body>: above the header, the dots and the page) ----------
   No dialog box: the picture flies out of the field onto the page (transform 420ms from the pressed picture's rectangle to the
   middle of the screen, as on the reference), the page behind falls back by a veil, four small + marks stand at the corners of the
   picture and a small katakana caption under it. Closing flies it back to where that picture is now. The page behind is locked
   (html.pf-lock) and inert. */

.pf-lb {
  --pf-lb-ms: 420ms;
  position: fixed;
  inset: 0;
  z-index: 100;
  color: var(--yh-paper-hi);
  overscroll-behavior: contain;
  visibility: hidden;
  pointer-events: none;
}
.pf-lb.is-open,
.pf-lb.is-flying {
  visibility: visible;
}
.pf-lb.is-open {
  pointer-events: auto;
}
.pf-lb:focus {
  outline: none;
}
.pf-lb__veil {
  position: absolute;
  inset: 0;
  background: var(--yh-ink);
  opacity: 0;
  transition: opacity var(--pf-lb-ms) ease;
}
.pf-lb.is-open .pf-lb__veil {
  opacity: 0.55;
}
.pf-lb__box {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  will-change: transform;
  visibility: hidden;
}
.pf-lb.is-open .pf-lb__box,
.pf-lb.is-flying .pf-lb__box {
  visibility: visible;
}
.pf-lb__img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  -webkit-user-drag: none;
  user-select: none;
}
.pf-lb__tick {
  position: absolute;
  width: 1ch;
  color: currentColor;
  font-family: var(--hud-mono);
  font-size: clamp(0.8rem, 1vw, 1rem);
  font-style: normal;
  line-height: 1;
  text-align: center;
  opacity: 0;
  transition: opacity 140ms ease;
  pointer-events: none;
}
.pf-lb.is-open .pf-lb__tick,
.pf-lb.is-open .pf-lb__jp {
  opacity: 1;
  transition: opacity 160ms ease var(--pf-lb-ms);
}
.pf-lb__tick--tl { top: -28px; left: -28px; }
.pf-lb__tick--tr { top: -28px; right: -28px; }
.pf-lb__tick--bl { bottom: -28px; left: -28px; }
.pf-lb__tick--br { right: -28px; bottom: -28px; }
.pf-lb__jp {
  position: absolute;
  top: calc(100% + 16px);
  left: 0;
  color: currentColor;
  font-family: var(--hud-mono);
  font-size: clamp(0.68rem, 0.8vw, 0.8rem);
  font-style: normal;
  letter-spacing: 0.08em;
  line-height: 1;
  white-space: nowrap;
  opacity: 0;
  transition: opacity 140ms ease;
  pointer-events: none;
}
.pf-lb__btn {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 1px solid var(--yh-paper-hi);
  border-radius: 0;
  background: transparent;
  color: var(--yh-paper-hi);
  font-family: var(--hud-mono);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.pf-lb.is-flying:not(.is-open) .pf-lb__btn {
  visibility: hidden;
}
.pf-lb__btn:hover,
.pf-lb__btn:focus-visible {
  background: var(--yh-paper-hi);
  color: var(--yh-ink);
}
.pf-lb__btn:focus-visible {
  outline: 2px solid var(--yh-paper-hi);
  outline-offset: 3px;
}
.pf-lb__close {
  top: clamp(8px, 2vh, 20px);
  right: clamp(8px, 2vw, 24px);
}
.pf-lb__prev,
.pf-lb__next {
  top: 50%;
  margin-top: -22px;
}
.pf-lb__prev { left: clamp(8px, 2vw, 24px); }
.pf-lb__next { right: clamp(8px, 2vw, 24px); }

/* ---------- small screens ---------- */

@media (max-width: 899px) {
  .pf-wrap { width: min(100%, max(30rem, 68vw)); }
}
@media (max-width: 719px) {
  .pf-label {
    position: relative;
    top: auto;
    left: auto;
    max-width: none;
    margin: clamp(24px, 8vw, 40px) 0 0 0;
  }
  .pf-wrap {
    width: 100%;
    padding-top: clamp(16px, 5vw, 28px);
  }
  .event-section {
    padding-right: 12px;
  }
  .pf-stripe {
    margin-right: -12px;
  }
  .pf-split { display: block; }
  .pf-split__media { margin: 0 -12px 0 calc(-1 * var(--pf-pl)); padding-top: clamp(16px, 5vw, 28px); }
  .pf-split .pf-wrap { margin-left: 0; }
  .pf-split--lore .pf-split__media { margin: 0; }
  .pf-split--lore .pf-wrap { width: 100%; }
  .pf-cover { width: calc(100% - var(--yh-g) - 12px); }
  .pf-stage { position: relative; width: 100%; height: min(112vw, 560px); margin: 8px 0 0; }
  .pf-stage__still { height: 100%; }
  .pf-gallery-slot.pf-gal--live { position: relative; height: clamp(400px, 118vw, 620px); border-right: 0; border-top: 1px solid var(--pf-soft); border-bottom: 1px solid var(--pf-soft); }
  .pf-lb__prev, .pf-lb__next { top: auto; bottom: 14px; margin-top: 0; }
  .charity-badge { margin-left: 0; }
}
@media (max-width: 400px) {
  .pf-title { font-size: 1.85rem; }
  .pf-btn { font-size: 0.72rem; }
}

/* ---------- reduced motion: no reveals, no scroll-driven bands, no scramble, instant states ---------- */

@media (prefers-reduced-motion: reduce) {
  .pf-wipe i,
  .pf-more,
  .pf-more__clip,
  .pf-more li,
  .pf-btn::before,
  .pf-btn__label,
  .pf-btn__arrow,
  .pf-wd--r,
  .pf-topfade,
  .pf-lb__veil,
  .pf-lb__tick,
  .pf-lb__jp,
  .pf-lb__btn,
  .pf-gal__link,
  .header-nav a::before {
    transition: none;
  }
  .pf-wipe i {
    transform: none;
  }
  .pf-wipe {
    display: none;
  }
  /* no bands to make room for: the sections end sooner */
  body.page-portfolio {
    --pf-end-pad: clamp(64px, 10vh, 120px);
  }
  .event-section {
    min-height: 0;
  }
}
