/* ricardolandim.com: the fonts, the tokens, and the designed site, ported from the prototype (docs/prototype/Main.dc.html,
   mounted as Sky-Intro.dc.html mounts it: Bodoni Moda with Outfit, the Mono theme).

   Everything for the designed site sits under .js, the mark the head's inline script puts on <html>: without it (no
   script, or one that never started) the same document is the plain scrolling page. */

/* The fonts are Google's own "latin" files (Bodoni Moda, Outfit), served from here. They are variable fonts: Bodoni Moda
   from 400 to 900 with an optical size from 6 to 96, Outfit from 100 to 900. The ?v=1 after each address is the number
   in RL_FONTS_VERSION (lib/assets.php): the page preloads exactly these addresses. */
@font-face {
  font-family: 'Bodoni Moda';
  font-style: normal;
  font-weight: 400 900;
  font-display: block;
  src: url(/assets/fonts/bodoni-moda.woff2?v=1) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Bodoni Moda';
  font-style: italic;
  font-weight: 400 900;
  font-display: block;
  src: url(/assets/fonts/bodoni-moda-italic.woff2?v=1) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 100 900;
  font-display: block;
  src: url(/assets/fonts/outfit.woff2?v=1) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Colors (Mono): /NOW is light with black text, THEN/ is dark with white text. */
  --now-bg: #f3f4f5;
  --now-ink: #000;
  --then-bg: #262626;
  --then-ink: #fff;
  /* The section lines: on the light side, and on the dark side. */
  --line-light: #3f3f3f;
  --line-dark: #c8c8c8;
  /* The glass behind a page: on desktop, and on the phone (more opaque there). */
  --glass-now-desktop: rgba(243, 244, 245, 0.86);
  --glass-then-desktop: rgba(24, 24, 27, 0.84);
  --glass-now-phone: rgba(243, 244, 245, 0.94);
  --glass-then-phone: rgba(24, 24, 27, 0.92);
  --glass-blur: 28px;
  /* The glows drift on one 34 second loop. */
  --drift: 34s;

  /* Type: Bodoni Moda and Outfit only, in normal case unless a style below says uppercase. */
  --serif: 'Bodoni Moda', Didot, 'Bodoni 72', serif;
  --sans: 'Outfit', Futura, 'Century Gothic', sans-serif;
  /* The top bar: Outfit 400, uppercase. */
  --bar-size: clamp(10px, 0.74vw, 13px);
  --bar-weight: 400;
  --bar-spacing: 0.12em;
  /* The small label: Outfit 400, uppercase, a little faded. */
  --label-size: clamp(9px, 0.66vw, 12px);
  --label-weight: 400;
  --label-spacing: 0.1em;
  --label-opacity: 0.6;
  /* The intro copy: Outfit 300. The section lines under the big words: the same, a size smaller (Ricardo's note of Nov 4;
     the prototype set them at the copy size). */
  --copy-size: clamp(14px, 1.08vw, 18px);
  --copy-weight: 300;
  --copy-leading: 1.55;
  --section-size: clamp(12px, 0.9vw, 15px);
  /* The words in the bars: what the prototype's white bar shows through its difference blend over each half (#fff less
     #f3f4f5, and #fff less #262626). The site sets these colors directly and blends nothing. */
  --bar-on-light: #0c0b0a;
  --bar-on-dark: #d9d9d9;
  /* The page's margin at the sides, and the bars' distance from the top and bottom. */
  --edge: 2.4vw;
  --bar-inset: 3.2vh;
  /* A card: 16:10, min(22vw, 40vh) wide. */
  --card-w: min(22vw, 40vh);
  /* The small type of the intro and the main view (the copy, the section lines and the card labels): on a screen shorter
     than 720px (DESKTOP.shortBelow in motion.js: a phone held sideways, a short window) the least and the most size of each
     shrink with the height, to height / 720 of what they are (18px is 2.5% of a 720px height), but the type no smaller than
     11px (the labels 8px). At 720 and taller nothing changes: each coefficient is rounded up, so there it's never under its
     px. */
  --intro-copy-size: max(11px, clamp(min(14px, 1.9444445vh), 1.08vw, min(18px, 2.5vh)));
  --intro-section-size: max(11px, clamp(min(12px, 1.6666667vh), 0.9vw, min(15px, 2.0833334vh)));
  --card-label-size: max(8px, clamp(min(9px, 1.25vh), 0.66vw, min(12px, 1.6666667vh)));
}
/* What sizes from the screen's height sizes from what's visible of it, where the browser can tell: in Safari on a phone, 100vh
   is the screen under its toolbars, 100dvh what they leave. Elsewhere the two are the same. */
@supports (height: 100dvh) {
  :root {
    --bar-inset: 3.2dvh; --card-w: min(22vw, 40dvh);
    --intro-copy-size: max(11px, clamp(min(14px, 1.9444445dvh), 1.08vw, min(18px, 2.5dvh)));
    --intro-section-size: max(11px, clamp(min(12px, 1.6666667dvh), 0.9vw, min(15px, 2.0833334dvh)));
    --card-label-size: max(8px, clamp(min(9px, 1.25dvh), 0.66vw, min(12px, 1.6666667dvh)));
  }
}

/* Hidden is hidden, whatever any rule below says about the element's display. */
[hidden] { display: none !important; }

/* Read by screen readers, never drawn. */
.vh { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

/* --- The plain page ------------------------------------------------------------------------------------------------------
   Without .js (no script, or one that didn't start within 4 s) the same document is a plain scrolling page, one column on
   the light ground: the name and About + Contact, "Creative and Maker" and its copy, each side's word, section lines and
   cards (each a link in its own colors, with its tag), then the open page's content or About. What the scripts would draw
   (the line, the canvases, the glows, the big ampersand, the counters) is left out, each section line shows once (the
   desktop wording), and no picture is wider than the screen. A card is a small tile in its color, holding its picture,
   with its title and tag beside it from the label row, and the whole row is its link; a card's loop, which only the script
   plays, is left out, so its tile shows its color rather than a blank box. */
:root:not(.js) body { max-width: 760px; margin: 0 auto; padding: 20px 20px 64px; font-family: var(--sans); font-weight: 300; line-height: 1.5; color: var(--now-ink); background: var(--now-bg); overflow-wrap: break-word; }
:root:not(.js) a { color: inherit; }
:root:not(.js) :is(.turn, .sky, [data-introbg], [data-introbg-trail], .atmo, .oamp, .ow, .bar.bot, .mean .p, .cname, .card video, .px, .pnum) { display: none; }
:root:not(.js) img, :root:not(.js) video { display: block; max-width: 100%; height: auto; }
:root:not(.js) .bar.top { display: flex; justify-content: space-between; gap: 1em; font-size: 13px; letter-spacing: 0.12em; text-transform: uppercase; }
:root:not(.js) .ostage .vh { position: static; width: auto; height: auto; margin: 40px 0 8px; clip: auto; overflow: visible; white-space: normal; font-family: var(--serif); font-size: 40px; font-weight: 400; line-height: 1.1; }
:root:not(.js) :is(.word, .ptitle, .axHead) { margin: 48px 0 8px; font-family: var(--serif); font-size: 36px; font-weight: 400; line-height: 1.1; }
:root:not(.js) .half.now .word, :root:not(.js) .wp[data-side="now"] .ptitle, :root:not(.js) .axNow { font-style: italic; }
:root:not(.js) .mean { margin: 0 0 16px; }
:root:not(.js) .cardw { position: relative; display: flex; align-items: center; gap: 16px; margin: 10px 0; }
:root:not(.js) .card { order: -1; flex: none; width: 96px; aspect-ratio: 16 / 10; border-radius: 6px; overflow: hidden; }
:root:not(.js) .card::after { content: ""; position: absolute; inset: 0; } /* the whole row opens the card's page */
:root:not(.js) .card img { width: 100%; height: 100%; object-fit: cover; }
:root:not(.js) .clabel { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.8em; }
:root:not(.js) :is(.clabel .g, .ik, .plab, .mcap, .axLab, .nl) { font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; opacity: 0.6; }
:root:not(.js) article.wp { margin-top: 48px; padding-top: 8px; border-top: 1px solid rgba(0, 0, 0, 0.15); }
:root:not(.js) .pinfo .cell { display: flex; gap: 1em; }
:root:not(.js) figure { margin: 24px 0; }
:root:not(.js) .pph { margin: 0 0 8px; }
:root:not(.js) .pph :is(img, video) { width: 100%; height: 100%; object-fit: cover; }
:root:not(.js) .pnext { display: flex; flex-wrap: wrap; gap: 1em; margin-top: 32px; }
:root:not(.js) .axUl { padding-left: 0; list-style: none; }

/* --- The main view (desktop; the phone's changes come below 600px) -------------------------------------------------------- */

/* The page itself never scrolls: the reels, the work pages and About move instead. */
.js, .js body { height: 100%; overflow: hidden; overscroll-behavior: none; }
.js body { margin: 0; background: var(--then-bg); font-family: var(--sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }

/* Two halves side by side: /NOW light on the left, THEN/ dark on the right. */
.js .stage { position: relative; width: 100vw; height: 100vh; height: 100dvh; overflow: hidden; overflow: clip; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); user-select: none; -webkit-user-select: none; }
/* A finger on the stage moves the reels (main.js), never the page: no panning, no pinching, no pull to refresh. */
.js .stage { touch-action: none; -webkit-tap-highlight-color: transparent; }
.js .stage a { color: inherit; text-decoration: none; }
/* Both clip rather than hide what overflows, so nothing (a focused card, say) can scroll them. */
.js .half { position: relative; overflow: hidden; overflow: clip; min-width: 0; }
.js .half.now { color: var(--now-ink); background: var(--now-bg); }
.js .half.then { color: var(--then-ink); background: var(--then-bg); }

/* The layers over the whole stage, kept out of the halves' grid. They come before the halves in the page, so each sits at
   the halves' words' level (z-index 1), which puts them over the halves and their glows and, in this order, under the big
   words and the cards: the intro's turning layer, the day-and-night layer, and the opener with its words.
   Day and night (sky.js) is a canvas 30% taller than the screen, which the script sizes and moves up with the big words;
   it shows only once the script fades it in. */
.js .sky { position: absolute; left: 0; top: 0; z-index: 1; width: 100%; height: 100%; pointer-events: none; opacity: 0; will-change: transform, opacity; }

/* The turning layer: the two sides of the dividing line, with a soft glow on each, larger than the screen so its corners
   never show as it turns. The script turns it: it waits a quarter turn back from its rest (40°) until the load swings it
   in, and has gone (opacity 0) by the time the main view's halves show the same sides. */
.js .turn {
  position: absolute; left: 50%; top: 50%; z-index: 1; width: 150vmax; height: 150vmax; pointer-events: none;
  background:
    radial-gradient(22% 18% at 33% 45%, rgba(70, 70, 70, 0.55) 0%, rgba(70, 70, 70, 0) 70%),
    radial-gradient(22% 18% at 67% 56%, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0) 70%),
    linear-gradient(90deg, var(--then-bg) 50%, var(--now-bg) 50%);
  transform: translate3d(-50%, -50%, 0) rotate(-50deg); will-change: transform, opacity;
}
/* Each side drawn on past the layer, two screens deep, so the screen is all one side when About's line lies a screen past
   its top or bottom (intro.js keeps it no further). In the layer they lie beyond its sides, so at any turn they're beyond
   the screen until the line slides there. */
.js .turn::before, .js .turn::after { content: ""; position: absolute; top: 0; bottom: 0; width: 200vh; }
.js .turn::before { right: 100%; background: var(--then-bg); }
.js .turn::after { left: 100%; background: var(--now-bg); }
.js body:not([data-start="intro"]) .turn { opacity: 0; } /* other addresses start on the main view */
.js body[data-start="intro"] .intro { opacity: 0; }       /* and only there do the big words wait for the turn */
/* /about starts with the line level under About, and the work out of sight. */
.js body[data-start="about"] .turn { opacity: 1; transform: translate3d(-50%, -50%, 0) rotate(270deg); }
.js body[data-start="about"] .intro, .js body[data-start="about"] [data-hint] { opacity: 0; }
.js body[data-start="about"] .rt { color: var(--bar-on-light); }

/* The opener: Creative on the dark side, Maker (italic) on the light side, the ampersand on the line between them, and the
   copy under each word. The script sizes and places them from their letters, and fades them in. */
.js .ostage { position: absolute; inset: 0; z-index: 1; pointer-events: none; }
/* The intro's background (introbg.js), under the opener's words: its drawing, and under that the particles' trails, a canvas
   the script adds. They show only once the script fades them in. */
.js [data-introbg], .js [data-introbg-trail] { position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; opacity: 0; }
.js .bl { display: inline-block; width: 0; height: 0; vertical-align: baseline; } /* a word's baseline, for the scripts */
.js .ow { position: absolute; left: 0; top: 0; margin: 0; white-space: nowrap; font-family: var(--serif); font-size: 150px; font-weight: 400; line-height: 0.86; letter-spacing: -0.02em; opacity: 0; will-change: transform, opacity, filter; }
.js .owC { color: var(--then-ink); }
.js .owM { color: var(--now-ink); font-style: italic; }
/* The ampersand's ink is split along the line: the light half's ground over the dark side (Ricardo's note of Nov 4) and the
   dark ink over the light side. The script draws the split as a gradient clipped to the letter. */
.js .oamp {
  position: absolute; left: 0; top: 0; margin: 0; white-space: nowrap; font-family: var(--serif); font-size: 220px; font-style: italic; font-weight: 400; line-height: 1; opacity: 0;
  color: transparent; background-image: linear-gradient(130deg, var(--now-bg) 50%, var(--bar-on-light) 50%); -webkit-background-clip: text; background-clip: text;
  will-change: transform, opacity;
}
.js .ocol { position: absolute; top: 70%; width: max-content; margin: 0; font-size: var(--intro-copy-size); font-weight: var(--copy-weight); line-height: var(--copy-leading); opacity: 0; will-change: transform, opacity, filter; }
.js .ocol span { display: block; white-space: nowrap; }
.js .ocolL { left: var(--edge); text-align: left; color: var(--then-ink); }
.js .ocolR { right: var(--edge); text-align: right; color: var(--now-ink); }

/* Each half's faint glows (the Mono theme), larger than the half so they can drift: back and forth on the spec's 34 s loop
   (Main.dc.html 33–36; its 38 s is the spec's 34 s here). */
@keyframes rl-drift { from { transform: translate3d(-4%, -3%, 0) rotate(0deg); } to { transform: translate3d(4%, 3%, 0) rotate(8deg); } }
.js .atmo { position: absolute; inset: -25%; z-index: 0; pointer-events: none; animation: rl-drift var(--drift) ease-in-out infinite alternate; }
/* They hold still while something covers the whole screen (main.js: a page on the phone, the film player), and carry on
   from there after. */
.js body.covered .atmo { animation-play-state: paused; }
.js .half.now .atmo {
  background:
    radial-gradient(40% 35% at 30% 25%, #ffffff 0%, rgba(255, 255, 255, 0) 70%),
    radial-gradient(45% 40% at 75% 80%, #e4e7ea 0%, rgba(228, 231, 234, 0) 70%),
    radial-gradient(35% 30% at 85% 15%, #eae8ef 0%, rgba(234, 232, 239, 0) 70%),
    var(--now-bg);
}
.js .half.then .atmo {
  background:
    radial-gradient(40% 35% at 70% 25%, #363636 0%, rgba(54, 54, 54, 0) 70%),
    radial-gradient(45% 40% at 25% 80%, #1a1a1a 0%, rgba(26, 26, 26, 0) 70%),
    radial-gradient(35% 30% at 15% 15%, #2f2d35 0%, rgba(47, 45, 53, 0) 70%),
    var(--then-bg);
}

/* The big word and its section lines, centered on the half's height; the scripts size them from the letters and move them.
   The intro's own font size is the copy size: the room under the word the scripts keep for the lines, as they took it at the
   prototype's size, so the word stays where it was now they're smaller and higher (scene.js, liftLines). */
.js .intro { position: absolute; top: 50%; left: var(--edge); right: var(--edge); z-index: 1; display: flex; flex-direction: column; gap: 3.2vh; gap: 3.2dvh; font-size: var(--intro-copy-size); pointer-events: none; will-change: transform; transform: translate3d(0, -50%, 0); }
.js .half.now .intro { align-items: flex-start; text-align: left; }
.js .half.then .intro { align-items: flex-end; text-align: right; }
.js .word { margin: 0; white-space: nowrap; font-family: var(--serif); font-size: min(13.6vw, 24.2vh); font-size: min(13.6vw, 24.2dvh); font-weight: 400; line-height: 0.86; letter-spacing: -0.02em; will-change: filter, opacity; }
.js .half.now .word { font-style: italic; }
.js .mean { margin: 0; font-size: var(--intro-section-size); font-weight: var(--copy-weight); line-height: var(--copy-leading); }
.js .half.now .mean { color: var(--line-light); }
.js .half.then .mean { color: var(--line-dark); }
.js .mean span { display: block; width: max-content; white-space: nowrap; }
.js .half.then .mean span { margin-left: auto; }
.js .mean .p { display: none; } /* desktop shows its own version of the lines (span.d) */

/* The reels: each card is placed by the scripts on its cylinder, and hidden until they do. */
.js .reel { position: absolute; inset: 0; z-index: 2; touch-action: none; cursor: grab; }
.js .reel:active { cursor: grabbing; }
.js .cardw { position: absolute; left: 50%; top: 50%; width: var(--card-w); margin-left: calc(var(--card-w) / -2); margin-top: calc(var(--card-w) * -0.3125); will-change: transform, opacity; backface-visibility: hidden; visibility: hidden; }
/* A card the keyboard focuses round the back of the cylinder, where it's hidden, stays focusable while its reel brings it
   round; it draws nothing until it gets there (the scripts give a hidden card opacity 0). */
.js .cardw.kb { visibility: visible !important; }
/* The label row above a card: its name, and its tag (status or client), on one line; a name too long for it ends in "…". In
   the small label's size (Ricardo's note of Nov 4; on a short screen, smaller, as above), with the top bar's weight and
   letter spacing. */
.js .clabel { position: absolute; left: 0; right: 0; bottom: 100%; margin-bottom: 0.85em; display: flex; justify-content: space-between; gap: 1em; font-size: var(--card-label-size); font-weight: var(--bar-weight); line-height: 1.2; letter-spacing: var(--bar-spacing); text-transform: uppercase; white-space: nowrap; }
.js .stage.short .clabel { display: none; } /* on a short screen (scene.js), none, as on the phone */
.js .clabel .t { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.js .clabel .g { flex: none; opacity: 0.62; }
/* The card whose page is open gains the slash: before its name on /NOW, after it on THEN/. */
.js .half.now .cardw.on .clabel .t::before { content: "/"; margin-right: 0.3em; }
.js .half.then .cardw.on .clabel .t::after { content: "/"; margin-left: 0.3em; }
.js .card { position: relative; display: block; width: 100%; aspect-ratio: 16 / 10; border-radius: 14px; overflow: hidden; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 26px 44px -30px rgba(0, 0, 0, 0.5); transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1); -webkit-user-drag: none; -webkit-touch-callout: none; }
.js .card:hover { transform: scale(1.035); }
.js .cimg { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; pointer-events: none; -webkit-user-drag: none; }
.js .cname { position: absolute; left: 7%; right: 7%; bottom: 9%; font-size: calc(var(--card-w) * 0.072); font-weight: 400; line-height: 1.02; letter-spacing: 0.05em; text-align: left; text-transform: uppercase; }

/* The bars: the name and About + Contact at the top, the Scroll hint and the counters at the bottom. */
.js .bar { position: absolute; left: 0; right: 0; z-index: 20; pointer-events: none; font-size: var(--bar-size); font-weight: var(--bar-weight); line-height: 1.3; letter-spacing: var(--bar-spacing); text-transform: uppercase; }
.js .bar a { color: inherit; text-decoration: none; pointer-events: auto; }
.js .bar a:focus-visible, .js .bar a[aria-current="page"] { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.3em; }
/* The underline under a pointer only where there is one to hover: a tap leaves a phone's link "hovered" until the next tap. */
@media (hover: hover) { .js .bar a:hover { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.3em; } }
.js .bar.top { top: var(--bar-inset); }
/* The bottom bar's Scroll hint and counters belong to the halves: they sit over the reels but under a work page's glass, so
   a page covers its half's counter as it covers the half's cards (the top bar stays over everything). */
.js .bar.bot { bottom: var(--bar-inset); z-index: 3; }
.js .nm { position: absolute; left: var(--edge); top: 0; color: var(--bar-on-light); }
.js .rt { position: absolute; right: var(--edge); top: 0; display: flex; gap: 2vw; color: var(--bar-on-dark); }
.js .hint { position: absolute; bottom: 0; right: calc(50% + 1.6vw); color: var(--bar-on-light); }
.js .act { position: absolute; bottom: 0; white-space: nowrap; opacity: 0; }
.js .actNow { right: calc(50% + 1.6vw); color: var(--bar-on-light); }
.js .actThen { left: calc(50% + 1.6vw); color: var(--bar-on-dark); }

/* --- Work pages (desktop) ------------------------------------------------------------------------------------------------
   A port of the prototype's panel and page (Main.dc.html 76–143, with its Bodoni and Outfit rules). A /NOW page covers the
   THEN/ half on the right as the dark glass, with its number and titles in italic; a THEN/ page (or a draft that says
   THEN/) covers the /NOW half on the left as the light glass. Each scrolls on its own. The scripts open and close them with
   "enter" and "leave", and "swap" when one replaces another on its side. */
.js .wp {
  position: fixed; top: 0; bottom: 0; width: 50vw; z-index: 10; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
  user-select: text; -webkit-user-select: text;
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.15); backdrop-filter: blur(var(--glass-blur)) saturate(1.15);
}
.js .wp[data-side="now"] { left: 50vw; background: var(--glass-then-desktop); color: var(--then-ink); --bar-thumb: rgba(255, 255, 255, 0.5); }
.js .wp[data-side="then"] { left: 0; background: var(--glass-now-desktop); color: var(--now-ink); --bar-thumb: rgba(0, 0, 0, 0.5); }
/* A page on the left ends on a whole pixel, rounded up from half the width: in a window an odd number of pixels wide, half of it
   ends on half a pixel, and the scrollbar, which the browser snaps to whole pixels, would then sit half a pixel off the middle
   of its gutter (Ricardo, Nov 5: more space on its left than its right). Rounded up, the page overlaps the dark half by at most
   half a pixel. */
.js .wp[data-side="then"] { width: round(up, 50vw, 1px); }
/* Its scrollbar is a thin line (Ricardo's notes of Nov 4 and 5): a 5px rounded thumb in the page's own ink at 50%, with 2px of the
   page's ground clear on every side of it (a 9px gutter, the thumb's transparent border), on no track, with
   no arrows, where the browser draws a bar of its own (Safari, and Chrome with "always show scrollbars"). Firefox, which has
   no ::-webkit-scrollbar, takes its thinnest bar in the same color. (Chrome reads the standard properties first and would
   then ignore the 9px, so it's given only the -webkit- ones.) */
.js .wp::-webkit-scrollbar { width: 9px; height: 9px; background: transparent; }
.js .wp::-webkit-scrollbar-track { background: transparent; }
.js .wp::-webkit-scrollbar-thumb { background: var(--bar-thumb); background-clip: padding-box; border: 2px solid transparent; border-radius: 5px; }
.js .wp::-webkit-scrollbar-button { display: none; width: 0; height: 0; }
.js .wp::-webkit-scrollbar-corner { background: transparent; }
@supports not selector(::-webkit-scrollbar) {
  .js .wp { scrollbar-width: thin; scrollbar-color: var(--bar-thumb) transparent; }
}
.js .wp a { color: inherit; }
.js .page { position: relative; display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); column-gap: 1.6vw; align-items: start; padding: 9vh 3vw 18vh 3vw; }

/* The hero: from the top edge, full bleed, 82% of the screen tall, solid for 55% then fading into the page, running 14vh
   under the title; the card's color shows until its picture does, or for good without one. Only the one its layout shows
   is drawn (.hwide or .hphone, below). */
.js .phero {
  position: relative; grid-column: 1 / -1; height: 82vh; margin: -9vh -3vw -14vh -3vw; overflow: hidden;
  /* No fade: the hero ends on a slanted edge, the slash's way, 18px over the label on the left (the label sits at 83% of the
     hero) and rising about 6° to the right, so the label and the title sit on the plain glass (Ricardo's comp, Nov 6). The
     phone keeps its fade, below. */
  --hero-cut: polygon(0 0, 100% 0, 100% calc(83% - 18px - 5.5vw), 0 calc(83% - 18px));
  -webkit-clip-path: var(--hero-cut); clip-path: var(--hero-cut);
}
/* The card's color, which shows until the picture does, is a layer of its own that stops 2px short of the cut, so it can't
   tint the cut's soft edge. */
.phero { background: var(--hero-bg); }
.js .phero { background: none; }
.js .phero::before {
  content: ""; position: absolute; inset: 0; background: var(--hero-bg);
  --hero-cut-in: polygon(0 0, 100% 0, 100% calc(83% - 20px - 5.5vw), 0 calc(83% - 20px));
  -webkit-clip-path: var(--hero-cut-in); clip-path: var(--hero-cut-in);
}
.js .phero img, .js .phero video { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; }

/* Close: a thin X (2px lines) on a 44px target, 3.2vh from the top, at the top left of the grid; on a page on the left it
   sits at the top right instead, by the seam, clear of the name. */
.js .px { position: absolute; top: 3.2vh; z-index: 3; width: 44px; height: 44px; color: inherit; }
.js .wp[data-side="now"] .px { left: 3vw; }
.js .wp[data-side="then"] .px { right: 3vw; }
.js .px::before, .js .px::after { content: ""; position: absolute; left: 50%; top: 50%; width: 60px; height: 2px; background: currentColor; transform: translate(-50%, -50%) rotate(45deg); }
.js .px::after { transform: translate(-50%, -50%) rotate(-45deg); }
.js .px:hover { opacity: 0.6; }

/* The section label (/NOW, THEN/ or Draft), then the number in columns 1–2 and the title in 3–6, over the hero's foot. */
.js .page > .ik { position: relative; z-index: 2; grid-column: 1 / 3; margin: 0 0 1.4vh 0; }
.js .ik { font-size: var(--label-size); font-weight: var(--label-weight); line-height: 1.3; letter-spacing: var(--label-spacing); text-transform: uppercase; opacity: var(--label-opacity); }
/* The floor is the phone's 30px: at the narrowest desktop window (600px), with the page's 9px scrollbar gutter, a one-word title such
   as Pocketwatch at 32px ran 2px past its columns. From 1000px wide, where 3.2vw passes 32px, nothing changes. */
.js .pnum, .js .ptitle { position: relative; z-index: 2; margin: 0; font-family: var(--serif); font-size: clamp(30px, 3.2vw, 58px); font-weight: 400; line-height: 1; letter-spacing: -0.01em; }
.js .pnum { grid-column: 1 / 3; }
.js .ptitle { grid-column: 3 / -1; }
.js .ptitle:focus { outline: none; } /* it takes the focus so the page is read from its title; it isn't something to press */
.js .wp[data-side="now"] .pnum, .js .wp[data-side="now"] .ptitle, .js .wp[data-side="now"] .nt { font-style: italic; }
.js .wp[data-draft] .page > .ik { grid-column: 1 / -1; } /* a draft has no number: its title goes on the row under the label */

/* The details: three cells with a hairline over each, a label over its value. */
.js .pinfo { grid-column: 1 / -1; margin-top: 3.4vh; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: 1.6vw; }
.js .pinfo .cell, .js .plab, .js .psum, .js .pnext { border-top: 1px solid rgba(127, 127, 127, 0.45); }
.js .pinfo .cell { padding-top: 0.8em; }
.js .iv { margin-top: 0.45em; font-size: clamp(12px, 0.95vw, 16px); font-weight: 300; line-height: 1.4; }

/* Project and the Line, 14vh below the details, so the first screen ends on them; then the body. */
.js .plab { grid-column: 1 / 3; margin-top: 14vh; padding-top: 0.8em; font-size: var(--label-size); font-weight: var(--label-weight); line-height: 1.3; letter-spacing: var(--label-spacing); text-transform: uppercase; opacity: var(--label-opacity); }
.js .psum { grid-column: 3 / -1; margin: 14vh 0 0 0; padding-top: 0.55em; font-size: clamp(17px, 1.5vw, 26px); font-weight: 300; line-height: 1.32; }
.js .pbody { grid-column: 3 / -1; margin-top: 3vh; font-size: var(--copy-size); font-weight: 300; line-height: 1.6; }
.js .pbody p { margin: 0 0 1em 0; }
/* Bold, typed as <b>, is 600 wherever it's used: the copy is light (300), and the browser's own bolder only reaches 400 (Nov 6). */
b, strong { font-weight: 600; }
.js .pbody a { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.2em; }
/* A Section (Nov 6), such as Results: its label like Project's, its text beside it like the body, a hairline over each. */
.js .psec { margin-top: 7vh; }
.js .psecb { margin-top: 7vh; padding-top: 0.55em; border-top: 1px solid rgba(127, 127, 127, 0.45); }

/* The rows: a Full row across the six columns with its caption in 1–2; a Split row's stacked pair beside its large
   picture, the two sides' widths from the server (each row's own grid-template-columns), the pair 2vw apart. Each picture
   fills its place, which is shaped as the row shapes it, cropped around its focus. */
.js .pmediaWrap { grid-column: 1 / -1; }
.js .mrow { margin: 6vh 0 0 0; display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); column-gap: 1.6vw; align-items: start; }
.js .pph { position: relative; width: 100%; border-radius: 2px; overflow: hidden; }
.js .pph img, .js .pph video { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; }
.js .mfull .pph { grid-column: 1 / -1; grid-row: 1; }
.js .mfull .mcap { grid-column: 1 / 3; grid-row: 2; }
.js .msplit .mleft { grid-column: 1; grid-row: 1; display: grid; row-gap: 2vw; }
.js .msplit .mbig { grid-column: 2; grid-row: 1; }
.js .msplit .capL { grid-column: 1; grid-row: 2; }
.js .msplit .capR { grid-column: 2; grid-row: 2; }
/* A grid (Nov 7): four pictures two to a line, as far apart both ways as a row's sides, with one caption under them, in 1–2 as a Full row's. */
.js .mgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.6vw; }
.js .mgrid .mcap { grid-column: 1 / -1; margin-top: calc(0.8em - 1.6vw); max-width: calc((100% - 5 * 1.6vw) / 3 + 1.6vw); }
.js .mcap { margin-top: 0.8em; font-size: clamp(10px, 0.78vw, 13px); font-weight: 300; line-height: 1.4; opacity: 0.7; }

/* The foot (Ricardo's notes of Nov 4): Back alone at the left, never underlined (the keyboard's focus shows as the
   outline), and at the right end one link to the next project, "Next/" in the small label style followed at once by its
   name in the title's type, not underlined; a long name wraps within the title's columns (3–6), right-aligned. Its slash
   is only drawn, and a hidden space parts the two for speech, so it's read "Next, <name>". */
.js .pnext { grid-column: 1 / -1; margin-top: 9vh; padding-top: 0.8em; display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); column-gap: 1.6vw; align-items: baseline; }
.js .nb { grid-column: 1 / 3; justify-self: start; text-decoration: none; font-size: var(--bar-size); font-weight: var(--bar-weight); letter-spacing: var(--bar-spacing); text-transform: uppercase; }
.js .nt { grid-column: 3 / -1; justify-self: end; text-align: right; text-decoration: none; font-family: var(--serif); font-size: clamp(20px, 2vw, 36px); font-weight: 400; line-height: 1.1; letter-spacing: -0.01em; }
.js .nl { font-family: var(--sans); font-size: var(--label-size); font-weight: var(--label-weight); font-style: normal; letter-spacing: var(--label-spacing); text-transform: uppercase; opacity: var(--label-opacity); }

/* Play (Ricardo's choice of Nov 5): a film's button covers the picture it plays from, a Full row's whole picture or the
   hero's solid part (its top 55%, clear of the title and the details under it), so a click or a tap anywhere on it plays
   the film; the loop or picture under it carries on. In its middle, a large thin outlined triangle (its 1px line drawn in
   the page, lib/front.php), centered on the picture, and the word Play 14px under it in the small label style, both white
   with a soft shadow, and a tight one so the 1px line keeps its edge, so they read on any picture. On a pointer, hovering
   brings them from 0.9 to full and grows them a little (1.04, over 200 ms; under reduced motion they don't grow). The keyboard's focus is the site's 1px outline, 4px out
   round the triangle and its word (a ring round the button itself would fall outside the picture, or the screen).
   The word's box sits under the triangle, and the same space again above it, so the triangle alone is centered. Without
   the scripts it has no player to open, so it isn't there. */
.js .film {
  --play: clamp(88px, 7.6vw, 116px); /* the glass disc's width (Ricardo's choice A, Nov 6) */
  position: absolute; inset: 0; z-index: 1; display: flex; align-items: center; justify-content: center; margin: 0; padding: 0;
  border: 0; border-radius: 0; background: none; color: #fff; font-family: var(--sans); font-size: var(--label-size); font-weight: var(--label-weight);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.js .phero .film { bottom: 45%; }
.js .film:focus-visible { outline: none; }
/* Play: a frosted glass disc centered on the picture, a thin white outlined triangle over the word Play inside it (Ricardo's
   choice A, Nov 6, with the outline back), so it reads on any picture, dark, light or busy. Under the triangle and the word sit two layers, a smoky tint over
   a blur, apart so that where a browser can't blur, the tinted disc still shows. */
.js .fplay {
  position: relative; isolation: isolate; box-sizing: border-box; width: var(--play); height: var(--play); border-radius: 50%;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: calc(var(--play) * 0.07);
  border: 1.5px solid rgba(255, 255, 255, 0.45); box-shadow: 0 2px 18px rgba(0, 0, 0, 0.16);
  transition: border-color 200ms ease-out;
}
/* (No opacity on the disc itself: that would stop the layer under it blurring the picture.) */
/* The two layers sit inside the ring: over it, they would blur it away whatever its width. */
.js .fplay::before, .js .fplay::after { content: ""; position: absolute; inset: 0; border-radius: 50%; }
.js .fplay::before { z-index: -1; background: rgba(24, 24, 24, 0.2); }
.js .fplay::after {
  z-index: -2; -webkit-backdrop-filter: blur(8px) saturate(1.1); backdrop-filter: blur(8px) saturate(1.1);
  transition: -webkit-backdrop-filter 200ms ease-out, backdrop-filter 200ms ease-out;
}
.js .fplay svg, .js .fword { opacity: 0.9; filter: drop-shadow(0 0 4px rgba(0, 0, 0, 0.3)); transition: opacity 200ms ease-out; }
/* The triangle a quarter of the disc tall, nudged right so its weight sits in the middle; the word a line under it. */
.js .fplay svg { display: block; overflow: visible; width: calc(var(--play) * 0.26 * 8 / 9); height: calc(var(--play) * 0.26); margin-left: calc(var(--play) * 0.045); }
.js .fword { display: block; line-height: 1.3; letter-spacing: var(--label-spacing); text-indent: var(--label-spacing); text-transform: uppercase; }
@media (hover: hover) {
  .js .film:hover .fplay { border-color: rgba(255, 255, 255, 0.9); } /* it doesn't grow: it blurs more (Nov 6) */
  .js .film:hover .fplay::after { -webkit-backdrop-filter: blur(24px) saturate(1.1); backdrop-filter: blur(24px) saturate(1.1); }
  .js .film:hover .fplay :is(svg, .fword) { opacity: 1; }
}
.js .film:focus-visible .fplay { outline: 1px solid currentColor; outline-offset: 4px; }
.js .film[data-quiet-focus]:focus-visible .fplay { outline: none; }
:root:not(.js) .film { display: none; }
/* Replay (Nov 9): a play-once video's button, out of sight (and out of the keyboard's way) until the video has ended, then
   fading in at the video's bottom right: a thin white circular arrow on a small frosted disc, Play's glass in little, over
   everything in its place, Play included. On the hero it sits at the right of the solid part, clear of the slanted edge
   (desktop) or the fade (phone). Pressing it hides it again while the video plays. Without the scripts it isn't there. */
.js .replay {
  --replay: 38px;
  position: absolute; right: 12px; bottom: 12px; z-index: 2; isolation: isolate; box-sizing: border-box; width: var(--replay); height: var(--replay);
  display: flex; align-items: center; justify-content: center; margin: 0; padding: 0; border: 1.5px solid rgba(255, 255, 255, 0.45); border-radius: 50%;
  background: none; color: #fff; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.16); cursor: pointer; -webkit-tap-highlight-color: transparent;
  opacity: 0; visibility: hidden; transition: opacity 300ms ease-out, visibility 0s linear 300ms, border-color 200ms ease-out;
}
.js .ended > .replay { opacity: 1; visibility: visible; transition: opacity 300ms ease-out, visibility 0s, border-color 200ms ease-out; }
.js .replay::before, .js .replay::after { content: ""; position: absolute; inset: 0; border-radius: 50%; }
.js .replay::before { z-index: -1; background: rgba(24, 24, 24, 0.2); }
.js .replay::after { z-index: -2; -webkit-backdrop-filter: blur(8px) saturate(1.1); backdrop-filter: blur(8px) saturate(1.1); }
.js .replay svg { display: block; width: 52%; height: 52%; opacity: 0.9; filter: drop-shadow(0 0 3px rgba(0, 0, 0, 0.3)); transition: opacity 200ms ease-out; }
@media (hover: hover) {
  .js .replay:hover { border-color: rgba(255, 255, 255, 0.9); }
  .js .replay:hover svg { opacity: 1; }
}
.js .replay:focus-visible { outline: 1px solid #fff; outline-offset: 3px; }
.js .replay[data-quiet-focus]:focus-visible { outline: none; }
.js .phero > .replay { right: 3vw; bottom: auto; top: calc(83% - 18px - 5.5vw - var(--replay) - 16px); }
:root:not(.js) .replay { display: none; }

/* The film player, over everything, the bars and the pages included: black, the X at the top right (as a page's, 2px
   lines on a 44px target), the film in the middle at 16:9, as large as fits in 92% of the width and 84% of the height.
   It fades in (over 150 ms under reduced motion) and goes at once, its film taken out so the sound stops (film.js). */
@keyframes rl-film-in { from { opacity: 0; } to { opacity: 1; } }
.js [data-filmplayer] { position: fixed; inset: 0; z-index: 30; display: grid; place-items: center; background: #000; color: #fff; animation: rl-film-in 300ms ease-out both; }
.js .ff { width: min(92vw, calc(84vh * 16 / 9)); aspect-ratio: 16 / 9; }
.js .ff video, .js .ff iframe { display: block; width: 100%; height: 100%; border: 0; background: #000; }
.js .fx { position: absolute; top: var(--bar-inset); right: var(--edge); width: 44px; height: 44px; margin: 0; padding: 0; border: 0; background: none; color: inherit; cursor: pointer; }
.js .fx::before, .js .fx::after { content: ""; position: absolute; left: 50%; top: 50%; width: 60px; height: 2px; background: currentColor; transform: translate(-50%, -50%) rotate(45deg); }
.js .fx::after { transform: translate(-50%, -50%) rotate(-45deg); }
.js .fx:hover { opacity: 0.6; }

/* Opening: the glass fades in over 420 ms while the page swings in from the seam over 760 ms. Closing: the page swings back
   toward the seam over 380 ms as the glass clears (320 ms, after 60), with the page let go of. A swap: the new page's glass
   is there at once and its page swings in after 120 ms, while the old page fades out over 200 ms above it, without glass. */
@keyframes rl-glass-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes rl-glass-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes rl-from-seam-left { from { opacity: 0; transform: perspective(1400px) translateX(-6vw) rotateY(16deg); } to { opacity: 1; transform: none; } }
@keyframes rl-from-seam-right { from { opacity: 0; transform: perspective(1400px) translateX(6vw) rotateY(-16deg); } to { opacity: 1; transform: none; } }
@keyframes rl-to-seam-left { from { opacity: 1; transform: none; } to { opacity: 0; transform: perspective(1400px) translateX(-4vw) rotateY(12deg); } }
@keyframes rl-to-seam-right { from { opacity: 1; transform: none; } to { opacity: 0; transform: perspective(1400px) translateX(4vw) rotateY(-12deg); } }
.js .wp.enter { animation: rl-glass-in 420ms ease-out both; }
.js .wp[data-side="now"].enter .page { animation: rl-from-seam-left 760ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.js .wp[data-side="then"].enter .page { animation: rl-from-seam-right 760ms cubic-bezier(0.22, 1, 0.36, 1) both; }
.js .wp.enter.swap { animation: none; }
.js .wp.enter.swap .page { animation-delay: 120ms; }
.js .wp.leave { pointer-events: none; animation: rl-glass-out 320ms 60ms cubic-bezier(0.3, 0, 0.8, 0.15) both; }
.js .wp[data-side="now"].leave .page { animation: rl-to-seam-left 380ms cubic-bezier(0.3, 0, 0.8, 0.15) both; }
.js .wp[data-side="then"].leave .page { animation: rl-to-seam-right 380ms cubic-bezier(0.3, 0, 0.8, 0.15) both; }
.js .wp.leave.swap { z-index: 11; background: transparent; -webkit-backdrop-filter: none; backdrop-filter: none; animation: none; }
.js .wp.leave.swap .page { animation: rl-glass-out 200ms ease-out both; }

/* --- About ----------------------------------------------------------------------------------------------------------------
   Over the whole screen, under the bars and the work pages, two ways (about.js picks; Ricardo, Nov 5): on the desktop layout
   720px tall and taller, the prototype's split screen (Main.dc.html 228–252, below); on the phone and on the desktop layout
   below 720px tall (a phone held sideways, a short window), one long scroll (.long, Ricardo's change of Nov 4, here). The
   content is the same: the /NOW part, /NOW, the Profile in columns 5–8, the /NOW copy in 9–10, Currently and Get in touch
   in 11–12; then the THEN/ part, THEN/, the Career in 5–8, the first list in 9–10, the rest in 11–12. The /NOW part's words
   start 12vh from its top, the THEN/ part's 7vh, and the first lines of every column line up at the top of their capitals.
   The words take the colors the prototype's difference blend gave them over each side; nothing is blended. The scripts
   fade it in and out (about.js).

   The long scroll: the /NOW part on the light ground, then the THEN/ part on the dark. Each part fills at least the
   screen, so About opens on light alone and ends on dark, each with 12vh clear at its foot. Each part's ground is the
   turning layer's side as it lies under About, its glow where the layer's is, so the two are one: the line, which intro.js
   keeps on the THEN/ part's edge, shows only while About fades. It scrolls the browser's own way, and holds its scroll to
   itself. On the desktop layout, where Get in touch can't hold a line on one line in columns 11–12 (.stack, about.js),
   9–10 and 11–12 move under the large paragraphs, side by side across 5–12, with a hairline between (below), and the
   large paragraphs widen to 5–12 too. */
.js [data-about] {
  position: fixed; inset: 0; z-index: 5; overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: none; will-change: opacity;
  scroll-padding-top: calc(var(--bar-inset) + 3.3 * var(--bar-size)); /* the band's height (.axBand), so a focused link never hides under it */
}
.js [data-about]::-webkit-scrollbar { display: none; }
.js [data-about]:focus { outline: none; } /* it takes the focus so it's read from its start, and scrolls by the keys; it isn't something to press */
.js .axHalf { position: relative; display: flex; align-items: flex-start; box-sizing: border-box; min-height: 100vh; min-height: 100dvh; padding-bottom: 12vh; }
.js .axTop {
  padding-top: 12vh; color: var(--bar-on-light);
  background: radial-gradient(27vmax 33vmax at calc(50% + 9vmax) calc(100% - 25.5vmax), rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0) 70%), var(--now-bg);
}
.js .axBot {
  padding-top: 7vh; color: var(--bar-on-dark);
  background: radial-gradient(27vmax 33vmax at calc(50% - 7.5vmax) 25.5vmax, rgba(70, 70, 70, 0.55) 0%, rgba(70, 70, 70, 0) 70%), var(--then-bg);
}
/* The words fade out as they reach the top bar (Ricardo's call, Nov 4): a band fixed at the top of About, under the bar's
   words, from the screen's top to 2em below their text, solid down to the bottom of their text and clearing over the 2em
   below. It draws the ground of the part under it, glow and all: light above the THEN/ part's edge (--ax-edge, from the
   screen's top, which about.js keeps) and dark below it, so the edge passes under the bar unbroken. It takes no clicks. */
.js .axBand {
  --ax-edge: 200vh; position: fixed; left: 0; right: 0; top: 0; z-index: 1; pointer-events: none;
  font-size: var(--bar-size); height: calc(var(--bar-inset) + 1.3em + 2em);
  background:
    radial-gradient(27vmax 33vmax at calc(50% - 7.5vmax) calc(var(--ax-edge) + 25.5vmax), rgba(70, 70, 70, 0.55) 0%, rgba(70, 70, 70, 0) 70%),
    linear-gradient(to bottom, transparent var(--ax-edge), var(--then-bg) var(--ax-edge)),
    radial-gradient(27vmax 33vmax at calc(50% + 9vmax) calc(var(--ax-edge) - 25.5vmax), rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0) 70%),
    var(--now-bg);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 2em), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 2em), transparent 100%);
}
.js .axGrid { width: 100%; display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 1.6vw; padding: 0 var(--edge); box-sizing: border-box; align-items: start; }
.js .axC5-8 { grid-column: 5 / 9; }
.js .axC9-10 { grid-column: 9 / 11; }
.js .axC11-12 { grid-column: 11 / 13; }
.js .stack .axGrid { row-gap: 6vh; }
.js .stack .axC5-8 { grid-row: 1; }
/* The stacked large paragraphs run as wide as the two small columns under them (Ricardo, Nov 5: a phone held sideways). Only
   the desktop layout's long scroll stacks, below 720px tall; a desktop window taller than that is the split screen. */
@media (min-width: 600px) { .js .long.stack .axC5-8 { grid-column: 5 / 13; } }
.js .stack .axC9-10 { grid-column: 5 / 9; grid-row: 3; }
.js .stack .axC11-12 { grid-column: 9 / 13; grid-row: 3; }
/* The rule (Ricardo's note of Nov 4): where the small columns sit under the large paragraphs, the work page's hairline
   across the grid, margin to margin, on a row of its own, so the space above it, from the paragraphs' last baseline (their
   last line trimmed to it), is the space below it, to the capitals of the columns' first lines (trimmed to them). None
   where the columns sit beside the paragraphs, or where there's nothing above it or below it. */
.js .axRule { display: none; border-top: 1px solid rgba(127, 127, 127, 0.45); }
.js .stack .axRule { display: block; grid-column: 1 / -1; grid-row: 2; }
.js .stack .axC5-8 > :last-child { margin-bottom: 0; text-box: trim-end cap alphabetic; }
.js .stack .axGrid > .axC5-8 > :only-child { text-box: trim-both cap alphabetic; }
.js .axGrid:has(> .axC5-8:empty) > .axRule, .js .axGrid:has(> .axC9-10:empty):has(> .axC11-12:empty) > .axRule { display: none; }
/* With no rule, the columns take its row, so it keeps no gap for it. */
.js .stack .axGrid:has(> .axC5-8:empty) > :is(.axC9-10, .axC11-12),
.js .stack .axGrid:has(> .axC9-10:empty):has(> .axC11-12:empty) > :is(.axC9-10, .axC11-12) { grid-row: 2; }
/* Only while about.js tries the columns: each line kept whole, the email's break after its @ (<wbr>, which Chrome breaks at
   even under nowrap) taken out. */
.js .fitting .axTouch li { white-space: nowrap; }
.js .fitting .axTouch wbr { display: none; }
.js .axHead, .js .axGrid > div > :first-child { text-box: trim-start cap alphabetic; }
/* The type, at the spec's sizes, each multiplied by the split screen's fit (--ax-fit, 1 on the long scroll). */
.js .axHead { grid-column: 1 / 3; margin: 0; font-family: var(--serif); font-size: calc(clamp(28px, 2.6vw, 46px) * var(--ax-fit, 1)); font-weight: 400; line-height: 0.9; letter-spacing: -0.01em; }
.js .axNow { font-style: italic; }
.js .axLab { margin: 0 0 1.4vh 0; font-size: calc(var(--label-size) * var(--ax-fit, 1)); font-weight: var(--label-weight); line-height: 1.3; letter-spacing: 0.12em; text-transform: uppercase; opacity: var(--label-opacity); }
.js .axGap { margin-top: 3.2vh; }
.js .axBig { margin: 0 0 1.2vh 0; font-size: calc(clamp(16px, 1.36vw, 24px) * var(--ax-fit, 1)); font-weight: 300; line-height: 1.32; }
.js .axTxt { margin: 0 0 1.1vh 0; font-size: calc(clamp(12px, 0.94vw, 16px) * var(--ax-fit, 1)); font-weight: 300; line-height: 1.55; }
.js .axUl { margin: 0; padding: 0; list-style: none; font-size: calc(clamp(12px, 0.94vw, 16px) * var(--ax-fit, 1)); font-weight: 300; line-height: 1.55; }
/* A word too long for its column (a name in a list, an address no layout can hold) breaks rather than running into the
   next column or off the screen; the email breaks after its @ first (lib/front.php). */
.js .axGrid { overflow-wrap: break-word; }
.js [data-about] a { color: inherit; text-decoration: none; }
.js [data-about] a:hover, .js [data-about] a:focus-visible { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.25em; }

/* The split screen (the desktop layout 720px tall and taller: not .long; about.js decides, and until it has, a desktop-wide
   screen shows this): the prototype's About as it was before the long scroll (Tasks 10 and 10b). The line lies level
   behind it across the middle of the screen, the light half on top, the dark half below; the halves draw no ground of their
   own, so the turning layer's two sides and glows are theirs. The top half's words start 12vh from the top, the bottom
   half's 7vh below the line. All of About's type is multiplied by one factor, --ax-fit, the same for both halves: 1, or the
   largest from 0.70 down at which both halves fit (about.js works it out); spacing in em follows it, while the grid, the
   margins and the 12vh / 7vh offsets stay. A half still taller than it at 0.70 scrolls on its own (.scrolls), fading at its
   edges, clear of the bar above. None of the long scroll's band, rule or stacking: the columns stay side by side, and the
   email breaks after its @ where its column can't hold it. */
@media (min-width: 600px) {
  .js [data-about]:not(.long) { --ax-fit: 1; overflow: visible; scroll-padding-top: 0; will-change: opacity, transform; }
  .js [data-about]:not(.long) .axBand, .js [data-about]:not(.long) .axRule { display: none; }
  .js [data-about]:not(.long) .axHalf {
    position: absolute; left: 0; right: 0; height: 50%; min-height: 0; box-sizing: border-box; padding-bottom: 4vh; background: none;
    overflow-x: hidden; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none;
  }
  .js [data-about]:not(.long) .axHalf::-webkit-scrollbar { display: none; }
  .js [data-about]:not(.long) .axTop { top: 0; padding-top: 12vh; }
  .js [data-about]:not(.long) .axBot { top: 50%; padding-top: 7vh; }
  .js [data-about]:not(.long) .axHalf.scrolls { user-select: none; -webkit-user-select: none; }
  .js [data-about]:not(.long) .axTop.scrolls {
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, transparent 7vh, #000 11vh, #000 calc(100% - 3vh), transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0, transparent 7vh, #000 11vh, #000 calc(100% - 3vh), transparent 100%);
  }
  .js [data-about]:not(.long) .axBot.scrolls {
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 5vh, #000 calc(100% - 3vh), transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0, #000 5vh, #000 calc(100% - 3vh), transparent 100%);
  }
}

/* Focus, as the prototype shows it: a 1px outline in the text color, a little way out. */
.js :focus-visible { outline: 1px solid currentColor; outline-offset: 4px; }
/* The focus given back after a click or a tap closed About, a page or the film (focus.js): no outline, no focus underline. */
.js [data-quiet-focus]:focus-visible { outline: none; }
.js .bar a[data-quiet-focus]:focus-visible { text-decoration: none; }

/* --- The stacked phone (below 600px wide) -------------------------------------------------------------------------------
   A port of the prototype's stacked phone (Phone-Horizontal.dc.html 21–75 and 184–200, as Phone-Horizontal-Intro.dc.html
   mounts it): the halves stacked as two bands of the visible screen (100dvh, whatever Safari's toolbars leave), light /NOW
   on top and dark THEN/ below, each with its own reel. The margins are 12px; the big word and its section lines sit 42% of
   the way down their band, 8px apart; the section lines are the phone's own (span.p), in Outfit Light at 12.5px. A card is
   240px wide at 16:10 with a 12px radius and no hover; its label row shows only in focus, fading in as it reaches the middle (Ricardo, Nov 10). The name and About + Contact sit on 44px targets at
   the top; /NOW's counter sits 10px above the division on the left, THEN/'s 10px below it on the right. There is no Scroll
   hint and no intro copy, and the turning layer has no glows: the line rests level, the dark side on top, and waits upright
   (a quarter turn back) for the load to swing it in. The glows are larger still and drift a little further (33–34). The
   scripts size the words, place the cards and move both reels down on a short screen (scene.js). */
@keyframes rl-drift-phone { from { transform: translate3d(-5%, -3%, 0) rotate(0deg); } to { transform: translate3d(5%, 3%, 0) rotate(10deg); } }
@media (max-width: 599.98px) {
  :root { --edge: 12px; --bar-inset: 0px; --card-w: 240px; }
  .js .stage { height: 100vh; height: 100dvh; grid-template-columns: minmax(0, 1fr); grid-template-rows: repeat(2, minmax(0, 1fr)); }
  .js .atmo { inset: -30%; animation-name: rl-drift-phone; }
  .js .turn { background: linear-gradient(90deg, var(--then-bg) 50%, var(--now-bg) 50%); transform: translate3d(-50%, -50%, 0) rotate(0deg); }
  .js [data-ocol], .js [data-hint] { display: none; }
  .js .intro { top: 42%; gap: 8px; }
  .js .word { font-size: 52px; }
  .js .mean { font-size: 12.5px; line-height: 1.4; }
  .js .mean .d { display: none; }
  .js .mean .p { display: block; }
  /* A reel moved down on a short screen (scene.js) still takes a finger anywhere in its band, above its own top too. */
  .js .reel::before { content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 50vh; }
  .js .clabel { opacity: var(--focus, 0); } /* only the card in focus shows its label row, faded by scene.js (Nov 10) */
  .js .card { border-radius: 12px; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08), 0 20px 34px -24px rgba(0, 0, 0, 0.5); transition: none; }
  .js .card:hover { transform: none; }
  .js .cname { left: 9%; right: 9%; bottom: 8%; font-size: 13px; line-height: 1.04; }
  .js .nm, .js .rt > * { min-height: 44px; display: inline-flex; align-items: center; }
  .js .rt { gap: 14px; }
  .js .bar.bot { top: 0; }
  .js .actNow { right: auto; left: var(--edge); bottom: calc(50% + 10px); }
  .js .actThen { left: auto; right: var(--edge); top: calc(50% + 10px); bottom: auto; }

  /* Work pages on the phone (Phone-Horizontal.dc.html 74–120, with its Bodoni and Outfit rules): full screen over both
     bands and the bars, in the phone's more solid glass, a /NOW page dark and a THEN/ page light. The grid: 4 columns 12px
     apart, 20px margins. The hero runs from the top edge, full bleed, 75% of the screen tall, solid for 52%, 12vh under
     the title; the X (36px of 2px lines on a 44px target) 10px from the top at the left of the grid; the section label
     across the width, then the number in column 1 and the title in 2–4, both 30px; the details as three rows, a label in
     the first quarter and its value beside it; Project and the Line 18vh below them; the body across the width. A Split
     row's large picture goes full width with its caption, the pair side by side under it at one height (each takes the
     width its shape asks, --r1 and --r2, 12px apart), then theirs. The foot: Back in column 1, and Next/ with the next
     project's name in columns 2–4, both on 44px targets. The slide in and out is below. */
  .js .wp, .js .wp[data-side] { left: 0; width: 100%; z-index: 25; -webkit-backdrop-filter: blur(26px) saturate(1.15); backdrop-filter: blur(26px) saturate(1.15); }
  .js .wp[data-side="now"] { background: var(--glass-then-phone); }
  .js .wp[data-side="then"] { background: var(--glass-now-phone); }
  .js .page { grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: 12px; padding: 6px 20px 80px 20px; }
  .js .phero {
    height: 75vh; margin: -6px -20px -12vh -20px; -webkit-clip-path: none; clip-path: none;
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 52%, rgba(0, 0, 0, 0) 100%); mask-image: linear-gradient(to bottom, #000 0%, #000 52%, rgba(0, 0, 0, 0) 100%);
  }
  .js .px, .js .wp[data-side] .px { top: 10px; left: 20px; right: auto; }
  .js .px::before, .js .px::after { left: 18px; width: 49px; }
  .js .ik { font-size: 9.5px; line-height: 1.35; padding-top: 0.2em; }
  .js .page > .ik, .js .wp[data-draft] .page > .ik { grid-column: 1 / -1; margin: 0 0 10px 0; }
  .js .pnum, .js .ptitle { font-size: 30px; line-height: 1.02; }
  .js .pnum { grid-column: 1 / 2; }
  .js .ptitle { grid-column: 2 / -1; }
  .js .pinfo { margin-top: 18px; grid-template-columns: minmax(0, 1fr); column-gap: 0; }
  .js .pinfo .cell { display: grid; grid-template-columns: 1fr 3fr; column-gap: 12px; padding: 9px 0; }
  .js .iv { margin-top: 0; font-size: 13px; }
  .js .plab { grid-column: 1 / 2; margin-top: 18vh; padding-top: 9px; font-size: 9.5px; line-height: 1.35; }
  .js .psum { grid-column: 2 / -1; margin-top: 18vh; padding-top: 7px; font-size: 17px; line-height: 1.35; }
  .js .pbody { grid-column: 1 / -1; margin-top: 16px; font-size: 15px; }
  .js .psec { grid-column: 1 / -1; margin-top: 40px; } /* on the phone a section's label has the row, its text under it, as the body is */
  .js .psecb { margin-top: 12px; padding-top: 0; border-top: 0; }
  .js .mrow { margin-top: 26px; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: 12px; }
  .js .mfull .mcap { grid-column: 1 / -1; }
  .js .mgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .js .mgrid .mcap { margin-top: -4px; max-width: none; }
  .js .mcap { margin-top: 8px; font-size: 11.5px; }
  .js .msplit { display: flex; flex-direction: column; align-items: stretch; }
  .js .msplit .mbig { order: 1; }
  .js .msplit .capR { order: 2; }
  .js .msplit .mleft { order: 3; display: flex; gap: 12px; align-items: flex-start; margin-top: 12px; }
  .js .msplit .capL { order: 4; }
  .js .msplit .mleft .pph { width: auto; min-width: 0; flex: var(--r1) 1 0; }
  .js .msplit .mleft .pph + .pph { flex: var(--r2) 1 0; }
  .js .pnext { margin-top: 40px; padding-top: 9px; grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: 12px; }
  .js .nb { grid-column: 1 / 2; font-size: 10px; display: flex; align-items: center; min-height: 44px; }
  .js .nt { grid-column: 2 / -1; font-size: 20px; padding: 11px 0; } /* a 44px target on one line */
  .js .nl { font-size: 9.5px; }
  .js .film { --play: 80px; font-size: 9.5px; }
  .js .phero::before { -webkit-clip-path: none; clip-path: none; }
  .js .phero .film { bottom: 48%; } /* the phone's solid part, its top 52% */
  .js .replay { --replay: 36px; right: 10px; bottom: 10px; }
  .js .phero > .replay { right: 20px; bottom: auto; top: calc(52% - var(--replay) - 12px); }
  /* The film player's X, as a page's on the phone, mirrored to the top right: its right edge on the 20px margin. */
  .js .fx { top: 10px; right: 20px; }
  .js .fx::before, .js .fx::after { left: 26px; width: 49px; }

  /* About on the phone (Phone-Horizontal.dc.html 206–232, as one long scroll): each part on a 4-column grid 12px apart
     with 12px margins, at least the screen's height. The /NOW part: /NOW in columns 1–2 with Get in touch beside it in
     3–4, so the email shows on the first screen; the Profile across the width 24px below; then the /NOW copy in 1–2 and
     Currently in 3–4. The THEN/ part: THEN/, the Career across the width, then the first list in 1–2 and the rest in 3–4.
     In each part the rule lies between the large paragraphs and the columns, 28px from each, as Ricardo drew it.
     The /NOW part's words start 66px from its top, the THEN/ part's 40px, each with 12vh clear at its foot. The phone's
     turning layer has no glows, so About's glows come in with its fade. Headings 30px,
     the large paragraphs 16px, the body 13px, the labels 9.5px. The links in Get in touch take a finger above the first and
     below the last, so each is a 44px target, and any between them are spaced to be one. */
  .js .axHalf { display: block; padding: 0 0 12vh 0; }
  .js .axBand { height: calc(22px + 0.65em + 2em); } /* the phone's bar words sit in the middle of 44px */
  .js [data-about] { scroll-padding-top: calc(22px + 2.65 * var(--bar-size)); } /* the band's height, as above */
  .js .axGrid { grid-template-columns: repeat(4, minmax(0, 1fr)); column-gap: 12px; row-gap: 24px; }
  .js .axTop .axGrid { padding-top: 66px; }
  .js .axBot .axGrid { padding-top: 40px; }
  .js .axHead { grid-column: 1 / 3; grid-row: 1; font-size: 30px; line-height: 1.02; }
  .js .axC5-8 { grid-column: 1 / -1; grid-row: 2; }
  .js .axRule { display: block; grid-column: 1 / -1; grid-row: 3; margin: 4px 0; } /* 24px of row gap and 4px each side */
  .js .axC5-8 > :last-child { text-box: trim-end cap alphabetic; }
  .js .axGrid > .axC5-8 > :only-child { text-box: trim-both cap alphabetic; }
  .js .axC9-10 { grid-column: 1 / 3; grid-row: 4; }
  .js .axBot .axC11-12 { grid-column: 3 / 5; grid-row: 4; }
  .js .axTop .axC11-12 { display: contents; }
  .js .axTouch { grid-column: 3 / 5; grid-row: 1; }
  .js .axCur { grid-column: 3 / 5; grid-row: 4; }
  .js .axTop .axGrid:has(> .axC9-10:empty):not(:has(.axCur)) > .axRule { display: none; } /* Get in touch is up by /NOW */
  /* With no rule, the columns take its row (as above). */
  .js .axGrid:has(> .axC5-8:empty) > :is(.axC9-10, .axC11-12), .js .axGrid:has(> .axC5-8:empty) .axCur,
  .js .axGrid:has(> .axC9-10:empty):has(> .axC11-12:empty) > :is(.axC9-10, .axC11-12),
  .js .axTop .axGrid:has(> .axC9-10:empty):not(:has(.axCur)) > .axC9-10 { grid-row: 3; }
  .js .axTouch > :first-child, .js .axCur > :first-child { text-box: trim-start cap alphabetic; }
  .js .axTouch .axGap { margin-top: 0; }
  .js .axLab { margin: 0 0 8px 0; font-size: 9.5px; }
  .js .axGap { margin-top: 20px; }
  .js .axBig { margin: 0 0 10px 0; font-size: 16px; }
  .js .axTxt { margin: 0 0 8px 0; font-size: 13px; line-height: 1.5; }
  .js .axUl { font-size: 13px; line-height: 1.5; }
  .js .axBig:last-child, .js .axTxt:last-child { margin-bottom: 0; }
  .js .axTouch a { position: relative; display: inline-block; }
  .js .axTouch a::before { content: ""; position: absolute; left: 0; right: 0; top: 0; bottom: 0; }
  .js .axTouch li:first-child a::before { top: -26px; }
  .js .axTouch li:last-child a::before { bottom: -26px; }
  .js .axTouch li:not(:first-child):not(:last-child) { padding: 12.25px 0; }
  .js .axTouch li:not(:first-child):not(:last-child) a::before { top: -12.25px; bottom: -12.25px; }
}

/* The phone's page motion (Phone-Horizontal.dc.html 74–77, and the design's page exits): a page and its glass slide in
   over 560 ms, a /NOW page from the right, a THEN/ page from the left, and slide back out the way they came in 400 ms
   on desktop's closing curve. A page another replaces (Next) fades out in 200 ms under the new one, which slides in over
   it. Under reduced motion the fades below take over, as on desktop. */
@keyframes rl-from-right { from { transform: translate3d(100%, 0, 0); } to { transform: none; } }
@keyframes rl-from-left { from { transform: translate3d(-100%, 0, 0); } to { transform: none; } }
@keyframes rl-to-right { from { transform: none; } to { transform: translate3d(100%, 0, 0); } }
@keyframes rl-to-left { from { transform: none; } to { transform: translate3d(-100%, 0, 0); } }
@media (max-width: 599.98px) and (prefers-reduced-motion: no-preference) {
  .js .wp[data-side].enter { animation: rl-from-right 560ms cubic-bezier(0.22, 1, 0.36, 1) both; }
  .js .wp[data-side="then"].enter { animation-name: rl-from-left; }
  .js .wp[data-side].leave { animation: rl-to-right 400ms cubic-bezier(0.3, 0, 0.8, 0.15) both; }
  .js .wp[data-side="then"].leave { animation-name: rl-to-left; }
  .js .wp[data-side].enter .page, .js .wp[data-side].leave .page { animation: none; }
  .js .wp[data-side].leave.swap {
    z-index: 24; animation: rl-glass-out 200ms ease-out both;
    -webkit-backdrop-filter: blur(26px) saturate(1.15); backdrop-filter: blur(26px) saturate(1.15);
  }
  .js .wp[data-side="now"].leave.swap { background: var(--glass-then-phone); }
  .js .wp[data-side="then"].leave.swap { background: var(--glass-now-phone); }
}

@media (prefers-reduced-motion: reduce) {
  .js .atmo { animation: none; } /* the glows stay put */
  /* Pages fade in and out over 150 ms, and nothing swings. */
  .js .wp.enter, .js .wp.enter.swap { animation: rl-glass-in 150ms ease-out both; }
  .js .wp.leave, .js .wp.leave.swap { animation: rl-glass-out 150ms ease-out both; }
  .js .wp .page { animation: none !important; }
  .js .card { transition: none; }
  .js [data-filmplayer] { animation-duration: 150ms; } /* the film player only fades, and quickly */
  .js .film:hover .fplay::after { transition: none; } /* Play brightens and blurs more at once */
  .js .replay, .js .ended > .replay { transition: none; } /* Replay shows at once */
  .js .turn { transform: translate3d(-50%, -50%, 0) rotate(40deg); } /* no swing in: the line starts at rest */
}
@media (prefers-reduced-motion: reduce) and (max-width: 599.98px) {
  .js .turn { transform: translate3d(-50%, -50%, 0) rotate(90deg); } /* the phone's rest: level, the dark side on top */
}

/* A hero with a phone version written as a second element: each layout draws only its own (the other asks for a 1px
   size, lib/front.php), with or without the scripts. */
@media (min-width: 600px) { .hphone { display: none; } }
@media (max-width: 599.98px) { .hwide { display: none; } }
