/* ===========================================================
   Nataliia — portfolio
   Palette: soft blush.  Change any colour once, here.
   =========================================================== */

:root {
  --paper:     #FBF6F5;
  --paper-2:   #F5ECEB;
  --pastel:    #F1E1DF;
  --card:      #FFFFFF;
  --ink:       #2A2528;
  --muted:     #7C6F72;
  --accent:    #C4718A;
  --pencil:    #A9707F;   /* the nav ring: graphite weight, still in the palette */
  --accent-ink:#A2506A;
  --line:      #E9DAD8;
  --shadow:    rgba(42, 37, 40, .13);

  /* --display: "Newsreader", Georgia, serif; */
  --body:    "Karla", "Helvetica Neue", Arial, sans-serif;
  --hand:    "Caveat", cursive;
  --mono:    "IBM Plex Mono", ui-monospace, monospace;

  --wrap: 1080px;
}

[data-theme="night"] {
  --paper:     #2A1A20;
  --paper-2:   #33212A;
  --pastel:    #432B36;
  --card:      #35232C;
  --ink:       #F6EAEE;
  --muted:     #C1A5AF;
  --accent:    #EE9DB2;
  --pencil:    #CE96A5;
  --accent-ink:#F5B2C3;
  --line:      #4A2F3B;
  --shadow:    rgba(0, 0, 0, .5);
}

* { box-sizing: border-box; }

html {
  scrollbar-gutter: stable; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  transition: background .4s ease, color .4s ease;
}

/* height:auto matters more than it looks. The images now carry their real
   width and height as attributes, which is what lets a slot be the right shape
   before the picture arrives - but with max-width alone, a container narrower
   than the image would shrink the width and leave the height at its stated
   value, squashing it. */
img { max-width: 100%; height: auto; }

/* the lamp's glow is deliberately wider than the lamp; on narrow screens it
   would otherwise push the page sideways, so let it run off the edge instead */
main { overflow-x: clip; }

a { color: var(--ink); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--ink); }

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

.wrap { max-width: var(--wrap); margin: 0 auto; padding: 0 24px; }
/* the desk carries sixteen things now, so the home page gets more room */
.wrap.hero, .wrap.scene-frame { max-width: 1260px; }

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--card); padding: 10px 16px; z-index: 99;
}
.skip:focus { left: 12px; top: 12px; }

/* ---------- typography ---------- */

h1, h2, h3 { font-family: var(--display); font-weight: 400; line-height: 1.15; margin: 0 0 .4em; }
h1 { font-size: clamp(42px, 8vw, 76px); letter-spacing: -.02em; }
h2 { font-size: clamp(26px, 3.4vw, 34px); }
h3 { font-size: 21px; }

p { margin: 0 0 1.1em; max-width: 62ch; }

.lead { font-size: 19px; color: var(--muted); }

.eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 14px;
}

/* ---------- navigation ---------- */

.nav {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}
.nav-inner {
  max-width: 1260px; margin: 0 auto; padding: 14px 24px;
  display: flex; align-items: center; gap: 20px;
  /* A fixed bar height, reserved on every page. The controls all have a set
     size, so the only thing that could ever change the header's height between
     pages is a web font arriving late - this holds the line so it can't. */
  min-height: 64px;
}
/* The name is the one that actually moved the menu. It is set in Caveat, and
   until Caveat arrives the browser draws it in whatever it has for `cursive` —
   about 49px wider. Its own left edge is pinned, so it does not appear to move;
   it just grows, and there are only about 13px of slack in the bar for the auto
   margin to give up, so the rest of the 49px shoves the whole menu to the right
   until the real font lands. Holding its width stops that at the source. */
.brand {
  font-family: var(--hand); font-size: 27px; line-height: 1; color: var(--ink);
  text-decoration: none; margin-right: auto; white-space: nowrap;
  width: 162px;
}
/* The bar is laid out from the right: the name takes the slack on the left, so
   anything that changes the width of the links or the switch slides everything
   else sideways. Text measured in a font is exactly that sort of thing — until
   Karla and IBM Plex Mono arrive the words are narrower, and the whole menu sat
   about 40px to the right and then snapped back the moment they landed. So the
   two text-sized boxes are given the width they have once the fonts are in, and
   they keep it whatever is drawing the words. Change a menu word and these want
   remeasuring: the number is simply the width of .nav-links on a wide screen. */
.nav-links {
  display: flex; gap: 6px; align-items: center;
  width: 628px; justify-content: space-between;
}
.nav-links a {
  position: relative; display: inline-block; white-space: nowrap;
  padding: 7px 13px; font-size: 15px; color: var(--muted); text-decoration: none;
  transition: color .25s ease;
}
/* The ring behind a nav word is a clean loop, drawn in the palette colour and
   masked with the grain lifted out of Nataliia's own pencil circle, so the line
   breaks up the way graphite does. The path starts at the top left and runs
   anticlockwise, ending just past where it began. */
.nav-links a svg {
  position: absolute; left: 0; top: 0; width: 100%; height: 100%;
  overflow: visible; pointer-events: none;
}
.nav-links a path {
  fill: none; stroke: var(--pencil);
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 1 2; stroke-dashoffset: 1;
  transition: stroke-dashoffset .7s cubic-bezier(.35, .05, .3, 1);
}
.nav-links a:hover, .nav-links a:focus-visible { color: var(--ink); }
.nav-links a:hover path, .nav-links a:focus-visible path { stroke-dashoffset: 0; }
.nav-links a[aria-current="page"] { color: var(--ink); }
.nav-links a[aria-current="page"] path { stroke-dashoffset: 0; }

/* the chaos / PM switch — both words stay visible, the active one darkens.
   Because neither word is ever hidden, the control never changes size. */
.mode-toggle {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--mono); font-size: 12px; letter-spacing: .06em;
  background: none; border: 0; padding: 6px 4px; cursor: pointer;
  color: var(--muted); white-space: nowrap;
  /* the width it settles at in IBM Plex Mono — held so the bar cannot move
     while the font is still on its way. It is the widest of the two boxes to
     change, at 26px, and it sits on every page now, so it matters most. */
  width: 233px; justify-content: flex-end;
}
.mode-word { transition: color .25s ease; }
.mode-toggle[aria-pressed="false"] .mode-word-chaos { color: var(--ink); }
.mode-toggle[aria-pressed="true"]  .mode-word-pm    { color: var(--ink); }

.mode-track {
  position: relative; flex: 0 0 auto;
  width: 38px; height: 20px; border-radius: 999px;
  background: var(--pastel); border: 1px solid var(--line);
  transition: border-color .2s ease;
}
.mode-toggle:hover .mode-track,
.mode-toggle:focus-visible .mode-track { border-color: var(--accent); }
.mode-knob {
  position: absolute; top: 2px; left: 2px;
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent);
  transition: transform .3s cubic-bezier(.2, .8, .2, 1);
}
.mode-toggle[aria-pressed="true"] .mode-knob { transform: translateX(18px); }

/* ---------- light / dark button ----------
   Variant B: a little lamp, lit in day mode and dark at night — the same idea
   as the big lamp on the home page. The icon markup lives in every .html file
   inside .theme-toggle. */

.theme-toggle {
  flex: 0 0 auto;
  display: inline-grid; place-items: center;
  /* the halo is positioned against this, not laid out inside it */
  position: relative;
  width: 36px; height: 36px;
  background: var(--card); color: var(--ink);
  border: 1px solid var(--line); border-radius: 999px;
  cursor: pointer; padding: 0;
  transition: border-color .2s ease, background .3s ease;
}
.theme-toggle:hover { border-color: var(--accent); }

.theme-toggle .icon-lamp {
  grid-area: 1 / 1; position: relative; z-index: 1;
  width: 19px; height: 19px;
  fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}
/* the warm pool of light behind the lamp — goes out in night mode */
/* The glow used to be a grid item beside the icon, at a fixed 34px. On a phone
   the button shrinks to 33px - 31px inside its border - so the halo no longer
   fitted, widened the grid track, and pushed the lamp 1.5px down and to the
   right of the button's centre. It also made the lit button look off-centre
   against the unlit one, which is what looked like movement when the lights
   were switched. Positioned instead of laid out, it cannot affect the icon at
   any size. */
.lamp-halo {
  position: absolute; left: 50%; top: 50%;
  margin: -17px 0 0 -17px;
  width: 34px; height: 34px; border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle,
    rgba(255, 205, 120, .75) 0%,
    rgba(255, 210, 140, .28) 45%,
    rgba(255, 210, 150, 0) 70%);
  transition: opacity .4s ease;
}
.lamp-beam { transition: opacity .4s ease; }
[data-theme="night"] .lamp-halo,
[data-theme="night"] .lamp-beam { opacity: 0; }

.nav-burger {
  display: none; background: none; border: 1px solid var(--line);
  border-radius: 8px; padding: 8px 11px; cursor: pointer; color: var(--ink);
}

/* ---------- desk scene ---------- */

.hero { padding-top: 48px; padding-bottom: 0; }

/* portrait | name + intro | lamp */
.hero-grid {
  display: flex;
  align-items: center;
  gap: clamp(16px, 3.4vw, 42px);
}

/* the portrait — cut out on the paper, bottom edge feathered away */
.hero-portrait {
  flex: 0 0 auto;
  align-self: center;
  width: clamp(140px, 19vw, 212px);
  height: auto;
  -webkit-mask-image: linear-gradient(to bottom, #000 84%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 84%, transparent 100%);
}

.hero-text { flex: 1 1 auto; min-width: 0; }
/* the intro stops short of the lamp so the cup has somewhere to stand */
.hero-text .lead { max-width: 31ch; }

/* the lamp — fixed in the hero, never moves with the modes */
.lamp {
  position: relative; display: block;
  flex: 0 0 auto;
  width: 163px;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  transform-origin: 50% 92%;
  transition: transform .45s cubic-bezier(.2,.8,.2,1);
}
/* A tap on a phone leaves :hover stuck on the last thing touched, so the lamp
   tilted on the first tap and stayed tilted for good. The tilt is now only
   offered where there is a real pointer to hover with. */
@media (hover: hover) and (pointer: fine) {
  .lamp:hover, .lamp:focus-visible { transform: rotate(-4deg); }
}

/* On a touch screen the lamp does not lean at all - a tap changes the light
   and nothing else moves. The tilt above is a hover flourish and hover does not
   exist here; left in any form it either sticks after a tap or makes the lamp
   jump each time the lights are switched. */
@media (hover: none) {
  .lamp, .lamp:hover, .lamp:focus, .lamp:focus-visible,
  [data-theme="night"] .lamp { transform: none; }
}
.lamp img {
  display: block; width: 100%; position: relative; z-index: 1;
  transition: opacity .55s ease, filter .55s ease;
  filter: drop-shadow(0 16px 22px var(--shadow));
}
.lamp .lamp-off { position: absolute; left: 0; top: 0; opacity: 0; z-index: 2; }
[data-theme="night"] .lamp .lamp-on { opacity: 0; }
[data-theme="night"] .lamp .lamp-off {
  opacity: 1;
  /* brightness only — it dims the lamp without cooling it, so the warm ceramic
     reads the same colour in both modes, just unlit */
  filter: brightness(.92) drop-shadow(0 16px 26px rgba(0,0,0,.5));
}

.lamp-glow {
  position: absolute; left: 50%; top: 27%;
  width: 210%; height: 140%;
  transform: translate(-50%, -50%);
  background: radial-gradient(circle at 50% 50%,
    rgba(255, 212, 128, .50) 0%,
    rgba(255, 214, 136, .24) 34%,
    rgba(255, 216, 150, .08) 54%,
    rgba(255, 216, 150, 0) 72%);
  filter: blur(6px);
  pointer-events: none; opacity: 1;
  transition: opacity .7s ease;
}
[data-theme="night"] .lamp-glow { opacity: 0; }
.hero h1 { margin-bottom: 12px; }
.signature {
  font-family: var(--hand); font-weight: 500;
  font-size: clamp(52px, 8vw, 90px);
  line-height: 1; letter-spacing: 0;
  margin-bottom: 18px;
}
.hero .lead { max-width: 40ch; }

.scene-frame { padding-bottom: 16px; margin-top: 30px; }

.scene {
  position: relative;
  /* the page anchors the top left with things lying on it */
  height: 690px;
  border-radius: 18px;
  transition: background .5s ease;
}

body.pm .scene {
  background-image: radial-gradient(circle, var(--line) 1.2px, transparent 1.2px);
  background-size: 28px 28px;
  border: 1px solid var(--line);
  /* two clear rows, then the page with everything long lying on it */
  height: 480px;
}

.obj {
  position: absolute;
  left: var(--x); top: var(--y);
  width: var(--w);
  transform: translateX(0) rotate(var(--r));
  background: none; border: 0; padding: 0; margin: 0;
  cursor: pointer;
  transition: left .85s cubic-bezier(.2,.8,.2,1), top .85s cubic-bezier(.2,.8,.2,1),
              width .85s cubic-bezier(.2,.8,.2,1), transform .85s cubic-bezier(.2,.8,.2,1);
}

.obj .art {
  display: block;
  animation: float var(--fd, 7s) ease-in-out infinite;
  animation-delay: var(--d, 0s);
  /* No will-change here. It pinned a permanent compositing layer on all sixteen
     objects, and on iOS such a layer is sometimes handed an opaque backing -
     which paints a white rectangle behind a cut-out that is not rectangular.
     That is the block that kept appearing after a tap (which changes z-index)
     or on coming back to the page (which re-rasterises restored layers); the
     pictures themselves are properly transparent, corners measured at
     rgba(0,0,0,0). The float animates transform, so the browser composites it
     while it runs anyway and the hint was buying nothing. */
  /* The music drives --beat on the scene, 0 to 1. It rides on the separate
     scale property rather than transform, so it lands instantly instead of
     being smeared by the transitions the float animation needs. */
  scale: calc(1 + var(--beat, 0) * var(--bt, .04));
}
.obj img { display: block; filter: drop-shadow(0 12px 16px var(--shadow)); }

/* The white block behind the cut-outs on a phone.

   The lamp settled it: it carries the same drop-shadow and never shows the
   block - and the one thing it does not do is float. The desk objects put a
   filter inside an element running a transform animation, and that pair is
   what iOS hands an opaque backing, painting the image's rectangle behind a
   shape that is not rectangular. Removing will-change was not enough because
   the animation composites the layer on its own.

   Either half can go. The shadow stays, because without it a cut-out looks
   pasted on rather than placed; the float is the half that is barely readable
   on a small screen anyway, and sixteen looping transforms are a real drain on
   a phone battery. Desktop keeps both. */
/* (the touch override for these lives at the foot of this file, where it can
   out-rank the per-object animations declared further down) */

/* The same label everywhere: the board's typewriter face, in both rooms, so
   flipping between them changes the arrangement and nothing else. It is set a
   step larger with a little more weight than the board had, because at 11px
   the mono face reads too faint against the desk. */
.obj .name {
  display: block; text-align: center; margin-top: 4px;
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  letter-spacing: .07em; text-transform: uppercase; color: var(--label);
  /* the label is allowed to be wider than the object it names, so a long one
     sits on one or two lines instead of stacking up as a column of words */
  width: max-content; max-width: 190px;
  margin-left: 50%; transform: translateX(-50%);
  opacity: 0; transition: opacity .25s ease;
}
.obj:hover .name, .obj:focus-visible .name,
.obj.showing-name .name { opacity: 1; }   /* the volume readout, briefly */
.obj img { transition: transform .3s ease; }
.obj:hover img, .obj:focus-visible img { transform: translateY(-7px) scale(1.03); }
.obj:hover { z-index: 5; }

/* A four-point drift rather than a straight up-and-down bob: it rises, hangs
   at the top, drops past the resting line and settles, with a little roll on
   the way. Each object gets its own height, speed and delay, so nothing on the
   desk keeps time with anything else. */
@keyframes float {
  0%   { transform: translateY(0) rotate(0deg); }
  28%  { transform: translateY(calc(var(--fa, 9px) * -0.78)) rotate(var(--fr, .7deg)); }
  52%  { transform: translateY(calc(var(--fa, 9px) * -1))    rotate(calc(var(--fr, .7deg) * .15)); }
  76%  { transform: translateY(calc(var(--fa, 9px) * -0.32)) rotate(calc(var(--fr, .7deg) * -.9)); }
  100% { transform: translateY(0) rotate(0deg); }
}
@keyframes wobble {
  0%, 100% { transform: rotate(0); }
  25%      { transform: rotate(-6deg); }
  75%      { transform: rotate(6deg); }
}
.obj.wobble .art { animation: wobble .5s ease; }

/* the cup gets lifted towards you instead of shaken */
@keyframes sip {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  32%      { transform: translateY(-11px) rotate(-8deg); }
  62%      { transform: translateY(-7px)  rotate(-5deg); }
}
.obj[data-object="tea-cup"].sipping .art { animation: sip 1s ease; }

/* The paint tube lies down in artistic chaos and stands up in PM mode, on one
   canvas at the same scale so it reads as the same tube. Clicking squeezes paint
   out: one scoop, then two, then three, then three rather larger. The three
   pictures share a colour, so each click looks like more of the same paint. */
.obj[data-object="paint-tube"] .art { display: grid; position: relative; }
.obj[data-object="paint-tube"] .art > img {
  grid-area: 1 / 1;
  transition: opacity .45s ease, transform .3s ease;
}
.obj[data-object="paint-tube"] .tube-stand { opacity: 0; }
body.pm .obj[data-object="paint-tube"] .tube-lay   { opacity: 0; }
body.pm .obj[data-object="paint-tube"] .tube-stand { opacity: 1; }

/* The paint lands just clear of the nozzle, which is at the lower left of the
   lying tube, and spreads onto the desk in front of it — beside the tube, never
   over it. The three pictures share a canvas and are aligned top-centre, so
   every pour starts at the same point under the nozzle and only the puddle
   grows. Sized against the barrel rather than the whole tube, so a squeeze
   looks like a squeeze. */
.obj[data-object="paint-tube"] .scoops {
  position: absolute; left: 27%; top: 99%;
  width: 36%; transform: translateX(-50%);
  display: grid; pointer-events: none;
}
.obj[data-object="paint-tube"] .scoop {
  grid-area: 1 / 1;
  display: block; width: 100%; height: auto;
  opacity: 0; transform: scale(.55);
  transform-origin: 50% 0;        /* it spreads out from where it lands */
  filter: drop-shadow(0 3px 4px var(--shadow));   /* a puddle sits flat, so a small shadow */
  transition: opacity .3s ease, transform .45s cubic-bezier(.2, .8, .2, 1);
}
.obj[data-object="paint-tube"].paint-1 .scoop-1,
.obj[data-object="paint-tube"].paint-2 .scoop-2,
.obj[data-object="paint-tube"].paint-3 .scoop-3 { opacity: 1; transform: none; }

/* PM mode keeps the desk clean: the tube stands there and stays capped */
body.pm .obj[data-object="paint-tube"] .scoops { display: none; }

/* The sketchbook starts shut in both modes and a click opens it; the next click
   shuts it again. Both pictures sit on one canvas, drawn to the same height and
   resting on the same line, so the covers spread from where the shut book was
   and it reads as that book opening rather than a swap for a different one. */
.obj[data-object="sketchbook"] .art { display: grid; }
.obj[data-object="sketchbook"] .art img {
  grid-area: 1 / 1;
  transition: opacity .55s ease, transform .3s ease;
}


/* The pencil has two poses on one canvas, cut from photographs taken without
   moving the camera: lying at an angle for artistic chaos, squared up flat for
   PM mode. A click swaps to the other one whichever mode you are in. */
.obj[data-object="pencil"] .art { display: grid; }
.obj[data-object="pencil"] .art img {
  grid-area: 1 / 1;
  opacity: 0;
  transition: opacity .35s ease, transform .3s ease;
}
.obj[data-object="pencil"][data-pose="angled"] .lead-angled,
.obj[data-object="pencil"][data-pose="flat"]   .lead-flat { opacity: 1; }

/* The headphones. Two poses like everything else — tipped over for artistic
   chaos, squared up for PM — but the click puts music on rather than turning
   them, so the label becomes the track name and three little bars keep time. */
.obj[data-object="headphones"] .art { display: grid; }
.obj[data-object="headphones"] .art img {
  grid-area: 1 / 1;
  opacity: 0;
  transition: opacity .35s ease, transform .3s ease;
}
/* the img type selector in the rule above outranks a bare class, so these
   name the element too rather than losing the cascade and showing nothing */
.obj[data-object="headphones"] .art img.cans-angled { opacity: 1; }
body.pm .obj[data-object="headphones"] .art img.cans-angled { opacity: 0; }
body.pm .obj[data-object="headphones"] .art img.cans-flat { opacity: 1; }

@keyframes eq { 0%, 100% { height: 3px; } 50% { height: 11px; } }

/* while something is playing the label stays up, so you can see what is on */
.obj[data-object="headphones"]:not([data-track="0"]) .art { animation: sway 3.4s ease-in-out infinite; }
@keyframes sway {
  0%, 100% { transform: rotate(-1.4deg) translateY(0); }
  50%      { transform: rotate(1.4deg) translateY(-4px); }
}
@media (prefers-reduced-motion: reduce) {
  .player .eq i,
  .obj[data-object="headphones"]:not([data-track="0"]) .art { animation: none; }
  .obj .art { scale: 1; }
}

/* One line rather than two loose buttons: bars, the track's name, skip and
   stop, in a pill that sits where the headphones' label would be. It only
   appears while something is playing, so the two never sit on top of each
   other. Its own buttons, because a button cannot nest inside another one. */
.player {
  position: absolute;
  left: var(--x); top: var(--y);
  transform: translateX(-50%);
  display: inline-flex; align-items: center; gap: 8px;
  padding: 3px 4px 3px 11px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--paper);
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .05em;
  color: var(--muted); white-space: nowrap;
  opacity: 0; pointer-events: none; z-index: 6;
  transition: opacity .3s ease, left .85s cubic-bezier(.2,.8,.2,1), top .85s cubic-bezier(.2,.8,.2,1);
}
body.music-on .player { opacity: 1; pointer-events: auto; }
body.pm .player { left: var(--px); top: var(--py); }
body.music-on .obj[data-object="headphones"] .name { opacity: 0; }
.player-track { min-width: 96px; }
.player button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0;
  border: 0; border-radius: 999px;
  background: none; color: var(--muted); cursor: pointer;
  transition: color .2s ease, background .2s ease;
}
.player button:hover, .player button:focus-visible { color: var(--accent); background: var(--pastel); }
.player svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.player .eq { display: inline-flex; align-items: flex-end; gap: 2px; height: 11px; }
.player .eq i {
  display: block; width: 2px; height: 4px; border-radius: 1px;
  background: var(--accent); animation: eq .9s ease-in-out infinite;
}
.player .eq i:nth-child(2) { animation-delay: .18s; }
.player .eq i:nth-child(3) { animation-delay: .36s; }

/* The same pair in the bar, so the music carries across the site and can be
   stopped from any page. */
.music-controls { display: inline-flex; align-items: center; gap: 4px; }
.music-controls button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0;
  border: 1px solid transparent; border-radius: 999px;
  background: none; color: var(--muted); cursor: pointer;
  transition: color .2s ease, border-color .2s ease, opacity .25s ease;
}
.music-controls button:hover,
.music-controls button:focus-visible { color: var(--accent); border-color: var(--line); }
.music-controls svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
body.music-on .music-play { color: var(--accent); }
.music-stop { opacity: .28; pointer-events: none; }
body.music-on .music-stop { opacity: 1; pointer-events: auto; }
.music-note-dot { opacity: 0; transition: opacity .25s ease; }
body.music-on .music-note-dot { opacity: 1; animation: eq 1s ease-in-out infinite; }

/* Both brushes work like the pencil: two poses on one canvas, photographed
   from the same spot and matched in length, so a click turns the brush on the
   spot instead of swapping it for a different one. */
.obj[data-object="brush-flat"] .art,
.obj[data-object="brush-fan"] .art,
.obj[data-object="brush-round"] .art { display: grid; }
.obj[data-object="brush-flat"] .art img,
.obj[data-object="brush-fan"] .art img,
.obj[data-object="brush-round"] .art img {
  grid-area: 1 / 1;
  opacity: 0;
  transition: opacity .35s ease, transform .3s ease;
}
.obj[data-object="brush-flat"][data-pose="angled"] .bristle-angled,
.obj[data-object="brush-flat"][data-pose="flat"]   .bristle-flat,
.obj[data-object="brush-fan"][data-pose="angled"]  .bristle-angled,
.obj[data-object="brush-fan"][data-pose="flat"]    .bristle-flat,
.obj[data-object="brush-round"][data-pose="angled"] .bristle-angled,
.obj[data-object="brush-round"][data-pose="flat"]   .bristle-flat { opacity: 1; }

/* The scissors have three poses on one canvas — shut, open, and standing to
   attention — cut from photographs taken without moving the camera, so the
   screw stays put and opening reads as the blades swinging rather than a swap.
   Which pose shows is set by data-pose in js/main.js, so the mode's resting
   pose and a clicked one never have to fight in the cascade. */
.obj[data-object="scissors"] .art { display: grid; }
.obj[data-object="scissors"] .art img {
  grid-area: 1 / 1;
  opacity: 0;
  transition: opacity .35s ease, transform .3s ease;
}
.obj[data-object="scissors"][data-pose="closed"]  .cut-closed,
.obj[data-object="scissors"][data-pose="open"]    .cut-open,
.obj[data-object="scissors"][data-pose="upright"] .cut-upright { opacity: 1; }

/* The camera is the one object with a picture for each mode — the angled shot
   for artistic chaos, the square-on one for PM. They sit on identical canvases
   and cross-fade, so the swap does not jump. */
.obj[data-object="camera"] .art { display: grid; }
.obj[data-object="camera"] .art img {
  grid-area: 1 / 1;
  transition: opacity .45s ease, transform .3s ease;
}
.obj[data-object="camera"] .cam-pm { opacity: 0; }
body.pm .obj[data-object="camera"] .cam-art { opacity: 0; }
body.pm .obj[data-object="camera"] .cam-pm { opacity: 1; }

/* PM mode — everything snaps to the board */
body.pm .obj {
  left: var(--px); top: var(--py);
  width: var(--pw);
  transform: translateX(-50%) rotate(0deg);
}
body.pm .obj .art { animation: none; scale: 1; }
body.pm .obj .name { opacity: 1; margin-top: 8px; }

.scene-note {
  font-family: var(--mono); font-size: 12px; color: var(--muted);
  letter-spacing: .04em; margin-top: 18px;
}

/* ---------- cards ---------- */

.section { padding-top: 64px; padding-bottom: 64px; border-top: 1px solid var(--line); }

.cards {
  display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  margin-top: 28px;
}
.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; padding: 24px;
  text-decoration: none; color: inherit; display: block;
  transition: transform .25s ease, border-color .25s ease;
}
.card:hover { transform: translateY(-3px); border-color: var(--accent); color: inherit; }
.card h3 { margin-bottom: 6px; }
.card p { font-size: 15px; color: var(--muted); margin: 0; }

/* ---------- galleries ---------- */

.gallery {
  display: grid; gap: 18px; margin-top: 36px;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
.gallery figure { margin: 0; }
.gallery button {
  display: block; padding: 0; border: 1px solid var(--line);
  background: var(--card); border-radius: 12px; overflow: hidden;
  cursor: pointer; width: 100%;
}
.gallery img { display: block; width: 100%; height: auto; }
.gallery figcaption {
  font-size: 14px; color: var(--muted); margin-top: 8px;
}

/* ---------- picture slots hold their place while loading ----------
   The photo wall has always done this; the other galleries now match. Each
   slot paints a tinted block at its final size straight away, and the picture
   fades in over it once it arrives. What made the page feel slow was not the
   waiting - it was empty space snapping into a picture, one after another.

   The fade is scoped to .js-fade, which js/main.js puts on <html>. If the
   script never runs, the pictures are simply visible from the start rather
   than stuck at opacity 0. */
.gallery button, .cert button, .case-shots button { background: var(--paper-2); }
.js-fade .gallery img,
.js-fade .cert img,
.js-fade .case-shots img { opacity: 0; transition: opacity .4s ease; }
.js-fade .gallery img.shown,
.js-fade .cert img.shown,
.js-fade .case-shots img.shown { opacity: 1; }

/* ---------- photography: masonry wall ----------
   Wider than the rest of the site and edge to edge, so the pictures carry the
   page. The columns are real elements, filled by js/gallery.js: a photograph is
   only put into a column once it has loaded and its true proportions are known,
   so a slot is never sized by guesswork and the wall never rearranges itself
   under the reader. The column count lives here and the script reads it. */

.shots-head { max-width: var(--wrap); margin: 0 auto; padding: 56px 24px 4px; }
.shots-head .lead { margin-bottom: 0; }

.shots-frame { max-width: 1560px; margin: 0 auto; padding: 34px 18px 8px; }

.shots {
  --shot-cols: 3;
  display: flex;
  align-items: flex-start;
  gap: 14px;
  opacity: 0;
  transition: opacity .4s ease;
}
.shots.ready { opacity: 1; }

.shots-col { flex: 1 1 0; min-width: 0; }

.shot {
  margin: 0 0 14px;
  opacity: 0;
  transition: opacity .5s ease;
}
.shot.in { opacity: 1; }

.shot button {
  display: block; width: 100%; padding: 0; margin: 0;
  border: 0; background: var(--paper-2);
  cursor: zoom-in; overflow: hidden;
  border-radius: 3px;
}
.shot img {
  /* the button carries the real aspect ratio, so the picture simply fills it */
  display: block; width: 100%; height: 100%; object-fit: cover;
  opacity: 0;
  transition: opacity .45s ease, transform .6s cubic-bezier(.2, .8, .2, 1);
}
.shot img.shown { opacity: 1; }
.shot button:hover img.shown,
.shot button:focus-visible img.shown { transform: scale(1.035); opacity: .92; }

.shots-more { height: 1px; }        /* the marker that asks for the next batch */

.shots-status {
  font-family: var(--mono); font-size: 12px; color: var(--muted);
  letter-spacing: .04em; margin: 0 0 18px; max-width: none;
}

@media (max-width: 980px) { .shots { --shot-cols: 2; } }
@media (max-width: 560px) {
  .shots { --shot-cols: 1; gap: 12px; }
  .shots-frame { padding: 26px 16px 8px; }
  .shots-head { padding: 40px 24px 4px; }
}

.lightbox {
  position: fixed; inset: 0; z-index: 90;
  background: rgba(20, 16, 18, .88);
  /* The room left around the picture, named once so the picture can be sized
     against what is actually left rather than against the whole screen. */
  --lb-pad: 24px;
  display: none; align-items: center; justify-content: center;
  padding: var(--lb-pad);
}
.lightbox.open { display: flex; }

/* A swipe inside the lightbox must not reach the page behind it. The body is
   pinned by the script while a picture is open; overscroll-behavior stops the
   scroll chaining out of the lightbox itself on the browsers that support it. */
body.page-held {
  position: fixed; left: 0; right: 0; width: 100%;
  overflow: hidden;
}
/* Not touch-action:none - the swipe handler lets a two-finger gesture through
   on purpose so a painting can still be pinched open, and that would block it.
   Pinning the body is what stops the page moving; this only keeps a scroll from
   chaining out of the lightbox on the browsers that honour it. */
.lightbox { overscroll-behavior: contain; }

/* This used to read 92vw / 86vh, which is a share of the whole screen and takes
   no account of the padding around it. On a 390px phone that made the picture
   359px wide inside a 342px space, and since the figure hides its overflow the
   extra 17px was simply sliced off the right of every painting and drawing.
   The height was never the problem - only the width is measured against the
   space that is actually free, so what the picture does on a wide screen is
   exactly what it did before. */
.lightbox img {
  max-width: calc(100vw - var(--lb-pad) * 2);
  max-height: 86vh;
  width: auto; height: auto;
  border-radius: 8px;
}
/* svh is the screen with the browser's own bars showing, so a picture taller
   than it is wide still fits before the address bar slides away. */
@supports (height: 100svh) {
  .lightbox img { max-height: 86svh; }
}

/* The picture carries its own line. As a flex item the figure shrinks to the
   width of the photograph rather than the width of the screen, which is what
   lets the line sit on the picture and stop at its edges. */
.lightbox-figure {
  position: relative; margin: 0;
  /* the same room as the picture it holds, so it never has to crop it */
  max-width: calc(100vw - var(--lb-pad) * 2);
  max-height: 86vh;
  min-width: 0;
  border-radius: 8px; overflow: hidden;
}
@supports (height: 100svh) {
  .lightbox-figure { max-height: 86svh; }
}
.lightbox-figure img { display: block; }
.lightbox-close {
  position: absolute; top: 18px; right: 20px; z-index: 2;
  background: none; border: 1px solid rgba(255,255,255,.5); color: #fff;
  border-radius: 999px; width: 40px; height: 40px; font-size: 18px; cursor: pointer;
}
/* The arrows are siblings of the picture, and the previous one sits before it
   in the markup — so on a narrow screen, where the picture is nearly the full
   width, the picture was painting straight over the top of it and the left
   arrow simply was not there. Both arrows are lifted above it. */
.lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 2;
  background: none; border: 1px solid rgba(255,255,255,.4); color: #fff;
  border-radius: 999px; width: 46px; height: 46px;
  font-size: 26px; line-height: 1; cursor: pointer;
  transition: border-color .2s ease, background .2s ease;
}
.lightbox-nav:hover { border-color: #fff; background: rgba(255,255,255,.12); }
.lightbox-nav.prev { left: 18px; }
.lightbox-nav.next { right: 18px; }
.lightbox-count {
  position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
  font-family: var(--mono); font-size: 12px; letter-spacing: .1em; color: rgba(255,255,255,.75);
}
.lightbox.solo .lightbox-nav, .lightbox.solo .lightbox-count { display: none; }

@media (max-width: 640px) {
  .lightbox-nav { width: 40px; height: 40px; font-size: 22px; }
  .lightbox-nav.prev { left: 8px; }
  .lightbox-nav.next { right: 8px; }
}

/* ---------- timeline & lists ---------- */

.timeline { border-left: 2px solid var(--line); padding-left: 24px; margin-top: 32px; }
.role { margin-bottom: 34px; position: relative; }
.role::before {
  content: ""; position: absolute; left: -31px; top: 9px;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--accent);
}
.role .when {
  font-family: var(--mono); font-size: 12px; letter-spacing: .08em;
  color: var(--muted); text-transform: uppercase;
}
.role h3 { margin: 4px 0 2px; }
.role .where { color: var(--muted); font-size: 15px; margin-bottom: 8px; }

.chips { display: flex; flex-wrap: wrap; gap: 8px; padding: 0; list-style: none; margin: 18px 0 0; }
.chips li {
  background: var(--pastel); color: var(--ink);
  border-radius: 999px; padding: 6px 14px; font-size: 14px;
}

.facts { list-style: none; padding: 0; margin: 18px 0 0; }
.facts li { padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 16px; }

/* ---------- form ---------- */

.form { max-width: 520px; margin-top: 32px; }
.field { margin-bottom: 20px; }
.field label {
  display: block; font-family: var(--mono); font-size: 12px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin-bottom: 7px;
}
.field input, .field textarea {
  width: 100%; font-family: var(--body); font-size: 16px; color: var(--ink);
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  padding: 12px 14px;
}
.field textarea { min-height: 150px; resize: vertical; }
.field input:focus, .field textarea:focus { border-color: var(--accent); outline: none; }
.hp { position: absolute; left: -9999px; }

.btn {
  font-family: var(--body); font-size: 16px; font-weight: 500;
  background: var(--accent); color: #fff; border: 0;
  border-radius: 10px; padding: 13px 26px; cursor: pointer;
  transition: background .2s ease;
}
.btn:hover { background: var(--accent-ink); }
/* it is a filled button, so it does not also want the underline every link gets */
.btn, .btn:hover { text-decoration: none; color: #fff; }

/* At night the accent is a pale pink, and white on it comes out at 2.08:1 —
   under half what text needs to be legible. The dark ground reads on it at
   7.97:1, so the lettering flips rather than the button colour. */
[data-theme="night"] .btn,
[data-theme="night"] .btn:hover { color: var(--paper); }
[data-theme="night"] .btn-tag { background: rgba(42, 26, 32, .17); }

/* ---------- the CV button ----------
   A plain filled rectangle said nothing about what pressing it does. This one
   carries the download mark, names the format in the typewriter face the rest
   of the site uses for small labels, and lifts under the pointer the same way
   the number cards above it do. */
.btn-cv {
  display: inline-flex; align-items: center; gap: 11px;
  padding: 13px 16px 13px 18px;
  border-radius: 999px;
  box-shadow: 0 10px 22px var(--shadow);
  transition: transform .25s ease, box-shadow .25s ease, background .2s ease;
}
.btn-cv:hover, .btn-cv:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 16px 30px var(--shadow);
}
.btn-icon {
  width: 18px; height: 18px; flex: 0 0 auto;
  fill: none; stroke: currentColor; stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round;
  transition: transform .25s ease;
}
/* the arrow dips towards the tray as you reach for it */
.btn-cv:hover .btn-icon, .btn-cv:focus-visible .btn-icon { transform: translateY(2px); }
.btn-tag {
  font-family: var(--mono); font-size: 10px; letter-spacing: .1em;
  padding: 3px 7px; border-radius: 6px;
  background: rgba(255, 255, 255, .22);
}

/* ---------- a figure that reads as a before and after ----------
   "1 week -> 2 days" was too long to sit on one line in its card and broke
   across three. Stacked, with the arrow turned to point down between them, it
   fits and reads as one thing becoming another. */
/* both classes are named so this outranks .proof-n, which sets display:block
   and sits further down the file than this does */
.proof-n.proof-n-stack {
  display: flex; flex-direction: column; align-items: center;
  gap: 1px; width: max-content; line-height: 1.1;
}
.proof-arrow {
  width: 22px; height: 22px; margin: -1px 0;
  fill: none; stroke: var(--accent); stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

/* the CV sits under the introduction now, so the page reads title, then what
   she does, then the thing to take away */
.cv-line { margin: 22px 0 0; }

/* ---------- the two places to find me, under the contact form ----------
   These used to be repeated in the footer of every page, which meant saying
   them twelve times to be read once. They sit here instead, beside the form,
   as the alternative to writing in it. */
.elsewhere {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: 12px;
}
.elsewhere a {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 11px 19px 11px 16px;
  border: 1px solid var(--line); border-radius: 999px;
  background: var(--card); color: var(--ink); text-decoration: none;
  box-shadow: 0 6px 16px var(--shadow);
  transition: transform .25s ease, box-shadow .25s ease,
              border-color .2s ease, color .2s ease;
}
.elsewhere a:hover, .elsewhere a:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 14px 26px var(--shadow);
  border-color: var(--accent); color: var(--accent-ink);
}
[data-theme="night"] .elsewhere a:hover,
[data-theme="night"] .elsewhere a:focus-visible { color: var(--accent); }
.elsewhere-icon {
  width: 20px; height: 20px; flex: 0 0 auto;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}

/* ---------- screenshots inside a case study ----------
   Thumbnails, not exhibits. They carry .gallery as well, so the lightbox in
   js/main.js picks the buttons up with no extra wiring and they open full
   screen exactly like a painting does - which is where a screenshot of a board
   becomes readable. The line each one shows when opened is its data-note.

   Cropped to a common shape so three very differently proportioned screenshots
   sit in a tidy row; nothing is lost, since the whole image is one click away. */
.case-shots {
  margin: 30px 0 0;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.case-shots img {
  aspect-ratio: 3 / 2; object-fit: cover;
  /* these are all much wider than 3:2, so the crop takes it off the sides -
     held to the left, where the titles and the first chart of each one are */
  object-position: left center;
}
/* the screenshots are white-backed, so in night mode they are knocked back a
   little rather than glaring out of a dark page */
[data-theme="night"] .case-shots img { opacity: .92; }
.case-shots figcaption {
  font-family: var(--mono); font-size: 11px; letter-spacing: .05em;
  line-height: 1.55; color: var(--muted); margin-top: 9px;
}

/* ---------- the head of the contact page ----------
   Title and standfirst on the left, the Instagram QR pinned to the right of
   them, above the form. On a narrow window the QR wraps underneath rather than
   squeezing the heading. */
.contact-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 30px; flex-wrap: wrap;
}
.contact-intro { flex: 1 1 380px; min-width: 0; }
/* The QR with the two buttons stacked directly under it. The column is only
   as wide as the QR card, so the buttons line up with its edges rather than
   setting their own width. */
.contact-links { flex: 0 0 auto; margin: 6px 0 0; width: 200px; }
.contact-links .qr { margin: 0; width: 100%; }
/* the buttons sit under the heading now, in a row of their own */
.contact-intro .elsewhere { margin: 16px 0; }

/* ---------- the Instagram QR ----------
   Small, because it is a shortcut rather than a feature. From a
   laptop screen you scan it to get the profile onto your phone;
   on a phone you tap it and the Instagram app opens. It earns its
   place on both, so it is not hidden on either.

   The white ground is fixed rather than themed: a scanner needs
   dark modules on a light field, so this one card stays light even
   at night. */
.qr { margin: 28px 0 0; padding: 0; width: max-content; }
.qr button {
  display: block; width: 100%; padding: 10px; cursor: zoom-in;
  border: 1px solid var(--line); border-radius: 14px;
  background: #FFFFFF;
  box-shadow: 0 6px 16px var(--shadow);
  transition: transform .25s ease, box-shadow .25s ease, border-color .2s ease;
}
.qr button:hover, .qr button:focus-visible {
  transform: translateY(-2px);
  box-shadow: 0 14px 26px var(--shadow);
  border-color: var(--accent);
}
/* Instagram's own code is a dense one, around 62 modules across, so it needs
   real estate: shown small each module lands on a pixel or two and a camera
   cannot resolve it. Square and unstretched, at the width of its column. */
.qr img { display: block; width: 100%; height: auto; }
.qr figcaption {
  font-family: var(--mono); font-size: 11px; letter-spacing: .05em;
  color: var(--muted); text-align: center; margin-top: 9px;
}

/* ---------- how touch behaves ----------
   manipulation keeps pinch-zoom and scrolling but drops double-tap-to-zoom,
   which was firing when someone tapped two desk objects in quick succession.
   The tap highlight is the grey box a phone paints over whatever you touch -
   on a cut-out lamp or a pair of headphones it appears as a square shadow
   around the picture, so it is turned off and the elements carry their own
   pressed state instead. */
a, button, .obj, summary, [role="button"] {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  /* iOS also draws a rounded box of its own when a touch is held on something
     it thinks you might want to copy or share. On a cut-out lamp or a pair of
     headphones that box is a grey square around a picture that is not square,
     which is the shadow people were seeing. */
  -webkit-touch-callout: none;
}

/* The desk objects are pictures inside buttons. Left selectable, a touch that
   drags even slightly is read as selecting an image, and iOS paints a tinted
   rectangle over it - again square, again nothing to do with the artwork. */
.obj img, .lamp img {
  -webkit-user-select: none; user-select: none;
  -webkit-user-drag: none;
}


/* ---------- the QR is desktop-only ----------
   Its whole purpose is getting the profile from a screen onto a phone. On a
   phone you cannot scan your own display, and the Instagram button is right
   there under the heading, so it is just weight. */
@media (max-width: 700px) {
  .contact-links { display: none; }
}

/* ---------- the gap under the header on a phone ----------
   Every page opened with 64px of air above its first line, which on a small
   screen reads as a blank band rather than a margin. About and the photograph
   wall already started at 40px - their first element is a picture, which fills
   the space instead of leaving it - and that is the spacing worth matching.
   Home is left alone: its desk needs the room it has. */
@media (max-width: 700px) {
  body:not([data-page="home"]):not([data-page="about"]) #main > .section:first-child {
    padding-top: 40px;
  }
  /* The case studies open on the back link rather than an eyebrow. Inline, it
     sits inside a line box and the leading drops the text several pixels below
     the padding edge, so those pages read as more spaced than the rest. Taking
     it out of the line box puts it on the padding edge like every other page;
     max-content keeps the click target the width of the words rather than the
     whole row. */
  #main > .section:first-child > .case-back:first-child {
    display: block; width: max-content;
  }
}

/* While the original is still on its way, the lightbox shows the copy from the
   grid scaled up. It is a smaller file, so it is a little soft - this takes the
   hard edge off that moment rather than letting it look like a broken image,
   and the class comes off the instant the real one lands. */
.lightbox img.softening { filter: blur(.4px); }

/* A formatting button that is currently in force. Same treatment as the art
   page's chosen filter, so "on" reads the same way across the site. */
.compose-form [data-cmd].is-on {
  background: var(--pastel);
  color: var(--accent-ink);
  border-color: var(--accent);
}

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

.footer {
  border-top: 1px solid var(--line); margin-top: 40px;
  padding: 34px 0; font-size: 15px; color: var(--muted);
}
.footer .wrap { display: flex; gap: 18px; flex-wrap: wrap; align-items: center; }
.footer a { margin-right: 16px; }
.footer .sig { margin-left: auto; font-family: var(--hand); font-size: 20px; }

/* ---------- responsive ---------- */

/* The bar now holds the name, six links and two controls, so the page links
   fold into the burger earlier than the rest of the layout changes. */
@media (max-width: 940px) {
  .nav-burger { display: block; }
  /* The name used to hold the slack, which pushed the burger to the far right
     against the music buttons - close enough to hit one while reaching for the
     other. The slack moves to the burger instead, so it sits beside the name
     with the whole gap between it and the controls. */
  .brand { margin-right: 12px; }
  .nav-burger { margin-right: auto; }
  .nav-links {
    display: none; position: absolute; top: 100%; left: 0; right: 0;
    background: var(--paper); border-bottom: 1px solid var(--line);
    flex-direction: column; gap: 0; padding: 8px 24px 18px;
    /* a dropped panel spans the bar, so left/right size it — not the desktop
       width above, which would otherwise win and leave it short */
    width: auto;
  }
  .nav-links.open { display: flex; }
  .nav-links a { align-self: flex-start; padding: 8px 13px; margin: 2px 0; font-size: 17px; }
}

/* just above that point everything still fits, but only just — tighten it */
@media (min-width: 941px) and (max-width: 1140px) {
  .nav-inner { gap: 14px; }
  .brand { font-size: 24px; width: 144px; }
  .nav-links a { font-size: 14px; padding: 6px 9px; }
  .mode-toggle { font-size: 11px; gap: 8px; }
  .theme-toggle { width: 33px; height: 33px; }
  /* the type is a step smaller in this band, so both boxes are remeasured */
  .nav-links { width: 545px; }
  .mode-toggle { width: 215px; }
}

/* narrow screens — the chaos / PM switch gets a row of its own */
/* Below 1261 the bar has more in it than fits on a line — the switch used to
   run off the right edge, and the squeeze the browser applied to fit it was
   itself font-dependent, which is the last thing that moved the menu. So the
   switch takes a row of its own from here down, exactly as it always has on a
   phone, and the top row is left with room to spare. */
@media (max-width: 1279px) {
  .nav-inner { flex-wrap: wrap; row-gap: 4px; }
  /* here it takes a row of its own, so it is the row that sets the width */
  .mode-toggle { order: 5; flex-basis: 100%; padding-left: 0; width: auto; justify-content: flex-end; }
}

@media (max-width: 860px) {

  .scene-note {
    display: none;

  }
  .mode-toggle {
    display: none;
  }
  /* portrait left, lamp right, text on its own line underneath */
  .hero-grid { flex-wrap: wrap; align-items: flex-end; gap: 16px; }
  .hero-portrait { order: 1; width: 128px; }
  .lamp { order: 2; width: 118px; margin-left: auto; }
  .hero-text { order: 3; flex: 1 1 100%; }

  .scene {
    position: static; height: auto;
    display: grid; grid-template-columns: repeat(3, 1fr);
    gap: 18px; padding: 20px 0;
  }
  .obj {
    position: static; width: 100% !important; transform: none !important;
    display: flex; flex-direction: column; align-items: center;
  }
  .obj .name { opacity: 1; }
  .obj .art { display: flex; justify-content: center; }
  .obj img { max-height: 120px; width: auto; }
  body.pm .obj img { max-height: 100px; }
  body.pm .scene { padding: 20px 12px; }
  .hero { padding-top: 36px; }
}

/* tablets — the hero row has spare width, so let the two objects breathe */
@media (min-width: 601px) and (max-width: 860px) {
  .hero-portrait { width: 172px; }
  .lamp { width: 152px; }
}

/* small phones */
@media (max-width: 540px) {
  .nav-inner { gap: 10px; padding: 12px 16px; }
  .brand { font-size: 22px; width: 132px; }
  .nav-burger { padding: 7px 9px; }
  .theme-toggle { width: 33px; height: 33px; }
  .mode-toggle { font-size: 11px; gap: 8px; }
  .nav-links { padding: 8px 16px 18px; }
}

@media (max-width: 480px) {
  .scene { grid-template-columns: repeat(2, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
  html { scroll-behavior: auto; }
}


/* ---------- the newer objects ---------- */

/* The pearl rose tube: lying with its cap off for artistic chaos, capped and
   standing for PM. Both frames come off the same shot, so it stands up on the
   spot. */
.obj[data-object="paint-pink"] .art { display: grid; }
.obj[data-object="paint-pink"] .art img {
  grid-area: 1 / 1; opacity: 0;
  transition: opacity .35s ease, transform .3s ease;
}
.obj[data-object="paint-pink"][data-pose="angled"] .art img.pink-lay,
.obj[data-object="paint-pink"][data-pose="flat"]   .art img.pink-stand { opacity: 1; }

/* The flesh tint tube has one pose and gets shaken instead. */
@keyframes shakeTube {
  0%, 100% { transform: translateX(0) rotate(0deg); }
  14%      { transform: translateX(-7px) rotate(-4deg); }
  32%      { transform: translateX(7px)  rotate(4deg); }
  52%      { transform: translateX(-5px) rotate(-3deg); }
  72%      { transform: translateX(4px)  rotate(2deg); }
  88%      { transform: translateX(-2px) rotate(-1deg); }
}
.obj[data-object="paint-three"].shaking .art { animation: shakeTube .75s ease; }

/* The page turns a little further anticlockwise on every click and lies flat
   again once it has come round to 45. It looks the same in both modes — it is
   just a sheet of paper — so there is no second pose. */
.obj[data-object="paper"] .art { transition: rotate .55s cubic-bezier(.2, .8, .2, 1); }
.obj[data-object="paper"][data-turn="1"] .art { rotate: -15deg; }
.obj[data-object="paper"][data-turn="2"] .art { rotate: -30deg; }
.obj[data-object="paper"][data-turn="3"] .art { rotate: -45deg; }

/* The planner leans on the desk and stands to attention on the board; a click
   swaps it either way, so there is no second picture to load. */
.obj[data-object="planner"] .art { transition: rotate .5s cubic-bezier(.2, .8, .2, 1); }
.obj[data-object="planner"][data-pose="angled"] .art { rotate: -9deg; }
.obj[data-object="planner"][data-pose="flat"]   .art { rotate: 0deg; }

/* On the board the pencil and the three brushes all lie the same way, one under
   the next. Their pictures carry a tall canvas so the angled pose fits, which
   would leave the label stranded far below the brush — so the label is pulled
   back up to sit under what you can actually see. */
body.pm .obj[data-object="brush-round"] .art { margin-bottom: -22px; }
body.pm .obj[data-object="brush-fan"] .art   { margin-bottom: -56px; }
body.pm .obj[data-object="brush-flat"] .art  { margin-bottom: -50px; }
body.pm .obj[data-object="pen"] .art         { margin-bottom: -60px; }

/* the planner is an svg, which has no pixel size of its own to fall back on */
.obj[data-object="planner"] .art img { display: block; width: 100%; height: auto; }

/* These four are long thin things drawn on a canvas big enough to hold them
   at an angle, so most of the box is empty — and an empty box still swallows
   clicks meant for whatever it is lying across. Clipping each pose down to a
   band around the brush itself lets the page underneath be clicked, which is
   what makes the overlapping composition usable rather than just pretty. */
.obj[data-object="brush-fan"][data-pose="flat"] .art { clip-path: polygon(89.6% 64.5%, -5.1% 70.2%, -6.1% 36.6%, 88.6% 30.9%); }
.obj[data-object="brush-fan"][data-pose="angled"] .art { clip-path: polygon(88.0% 22.5%, 20.3% 118.4%, 2.0% 91.4%, 69.6% -4.5%); }
.obj[data-object="brush-flat"][data-pose="flat"] .art { clip-path: polygon(94.3% 63.3%, -5.6% 68.3%, -6.3% 37.1%, 93.6% 32.1%); }
.obj[data-object="brush-flat"][data-pose="angled"] .art { clip-path: polygon(-1.8% 9.1%, 75.5% 103.9%, 90.4% 76.7%, 13.1% -18.1%); }
.obj[data-object="brush-round"][data-pose="flat"] .art { clip-path: polygon(-5.7% 68.5%, 106.3% 78.3%, 107.0% 31.8%, -5.0% 21.9%); }
.obj[data-object="brush-round"][data-pose="angled"] .art { clip-path: polygon(106.2% 15.8%, 3.5% 124.9%, -4.2% 82.0%, 98.4% -27.2%); }
.obj[data-object="pencil"] .art { clip-path: polygon(-6.0% 131.8%, 105.9% 131.8%, 105.9% 21.0%, -6.0% 21.0%); }

/* The clip above shapes the picture, but the button around it is still a full
   rectangle and it is the button that catches the pointer. So the button steps
   out of the way and hands hit-testing to the clipped art and the label; the
   click still bubbles up to it, and hovering a child still counts as hovering
   the button. */
.obj[data-object="brush-fan"],
.obj[data-object="brush-flat"],
.obj[data-object="brush-round"],
.obj[data-object="pencil"] { pointer-events: none; }
.obj[data-object="brush-fan"] .art,   .obj[data-object="brush-fan"] .name,
.obj[data-object="brush-flat"] .art,  .obj[data-object="brush-flat"] .name,
.obj[data-object="brush-round"] .art, .obj[data-object="brush-round"] .name,
.obj[data-object="pencil"] .art,      .obj[data-object="pencil"] .name { pointer-events: auto; }

/* The pen, standing up in artistic chaos and lying down on the board. Same
   handling as the pencil: two frames off one shot, matched in length so it
   turns on the spot, and clipped to a band so its mostly-empty box does not
   swallow clicks meant for whatever it is lying across. */
.obj[data-object="pen"] .art { display: grid; }
.obj[data-object="pen"] .art img {
  grid-area: 1 / 1; opacity: 0;
  transition: opacity .35s ease, transform .3s ease;
}
.obj[data-object="pen"][data-pose="angled"] .art img.nib-angled,
.obj[data-object="pen"][data-pose="flat"]   .art img.nib-flat { opacity: 1; }
.obj[data-object="pen"] .art { clip-path: polygon(106.6% 59.2%, 2.1% 63.4%, 1.1% 40.6%, 105.7% 36.4%); }
.obj[data-object="pen"] { pointer-events: none; }
.obj[data-object="pen"] .art, .obj[data-object="pen"] .name { pointer-events: auto; }

/* The four clipped tools kept losing a slice of themselves on hover and on a
   click: the lift is a transform on the image, but the clip belongs to the box
   around it, so the picture slid out from under its own mask. The lift moves
   the whole box instead, using the separate translate property so it does not
   fight the float animation on transform or the beat on scale. */
.obj[data-object="pen"]:hover img,
.obj[data-object="pencil"]:hover img,
.obj[data-object="brush-fan"]:hover img,
.obj[data-object="brush-flat"]:hover img,
.obj[data-object="brush-round"]:hover img { transform: none; }
.obj[data-object="pen"] .art,
.obj[data-object="pencil"] .art,
.obj[data-object="brush-fan"] .art,
.obj[data-object="brush-flat"] .art,
.obj[data-object="brush-round"] .art { transition: translate .3s ease; }
.obj[data-object="pen"]:hover .art,
.obj[data-object="pencil"]:hover .art,
.obj[data-object="brush-fan"]:hover .art,
.obj[data-object="brush-flat"]:hover .art,
.obj[data-object="brush-round"]:hover .art { translate: 0 -7px; }

/* The page is the surface, so it never comes forward — not even on hover — and
   the things lying on it always sit above it. */
.obj[data-object="paper"] { z-index: 0; }
.obj[data-object="paper"]:hover,
.obj[data-object="paper"]:focus-visible,
.obj[data-object="paper"]:focus-within { z-index: 0; }
.obj[data-object="pencil"], .obj[data-object="pen"],
.obj[data-object="brush-fan"], .obj[data-object="brush-flat"],
.obj[data-object="brush-round"], .obj[data-object="sticky-notes"] { z-index: 4; }

/* The sketchbook now follows the same two-pose scheme as everything else:
   open on the desk, shut on the board, and a click swaps it either way. */
.obj[data-object="sketchbook"] .art img { opacity: 0; }
.obj[data-object="sketchbook"][data-pose="angled"] .art img.book-open,
.obj[data-object="sketchbook"][data-pose="flat"]   .art img.book-closed { opacity: 1; }

/* On the board the tools are pinned in place — a click still sounds, but
   nothing moves, so the row stays tidy. */


/* On the board the page lies square and stays that way: a click still sounds
   but it does not turn, so the row of tools on it never goes crooked. */
body.pm .obj[data-object="paper"] .art { rotate: 0deg; }
body.pm .obj[data-object="pen"] .art { margin-bottom: -95px; }

/* A line about the picture, written across the foot of the picture itself. It
   lies on a gradient that fades away upwards, so the words stay readable
   whether the photograph ends in bright sky or black shadow, and the picture is
   still visible through it. It is deliberately not a solid bar. */
.lightbox-note {
  position: absolute; left: 0; right: 0; bottom: 0;
  margin: 0; padding: 52px 22px 16px;
  background: linear-gradient(to top,
    rgba(10, 7, 9, .86) 0%,
    rgba(10, 7, 9, .58) 42%,
    rgba(10, 7, 9, 0) 100%);
  font-family: var(--mono); font-size: 13px; line-height: 1.55;
  letter-spacing: .04em; color: rgba(255, 255, 255, .95);
  text-align: center; text-wrap: balance;
  pointer-events: none;      /* the arrows and the picture stay clickable */
}
.lightbox-note[hidden] { display: none; }

@media (max-width: 640px) {
  .lightbox-note { font-size: 12px; padding: 40px 14px 13px; }
  /* less room given away to margin on a small screen, so the picture is bigger */
  .lightbox { --lb-pad: 12px; }
}

/* ---------- contact: a new email rather than a form ---------- */
.compose {
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--paper); overflow: hidden;
  box-shadow: 0 18px 40px rgba(0,0,0,.06);
}
.compose-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 12px 16px;
  border-bottom: 1px solid var(--line); background: var(--pastel);
}
.compose-title { font-family: var(--mono); font-size: 13px; letter-spacing: .08em; color: var(--ink); }
.compose-expand {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; padding: 0; cursor: pointer;
  border: 1px solid transparent; border-radius: 8px; background: none; color: var(--muted);
}
.compose-expand:hover, .compose-expand:focus-visible { color: var(--accent); border-color: var(--line); }
.compose-expand svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }

.compose-form { padding: 4px 16px 18px; display: flex; flex-direction: column; }
.compose-row {
  display: flex; align-items: baseline; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid var(--line);
}
.compose-key {
  flex: 0 0 74px; font-family: var(--mono); font-size: 12px;
  letter-spacing: .08em; color: var(--muted);
}
.compose-to { color: var(--ink); }
.compose-row input {
  flex: 1 1 auto; min-width: 0; border: 0; background: none;
  font: inherit; color: var(--ink); padding: 2px 0;
}
.compose-row input:focus { outline: none; }
.compose-row:focus-within { border-bottom-color: var(--accent); }

.compose-tools {
  position: relative; display: flex; align-items: center; gap: 4px;
  flex-wrap: wrap; padding: 10px 0;
}
.compose-tools button {
  min-width: 30px; height: 30px; padding: 0 8px; cursor: pointer;
  border: 1px solid transparent; border-radius: 8px;
  background: none; color: var(--muted); font: inherit; font-size: 14px;
}
.compose-tools button:hover, .compose-tools button:focus-visible {
  color: var(--accent); border-color: var(--line);
}
.compose-clear { font-family: var(--mono); font-size: 11px; letter-spacing: .08em; }
.compose-sep { width: 1px; height: 18px; background: var(--line); margin: 0 4px; }
.compose-emoji {
  position: absolute; top: 38px; left: 60px; z-index: 3;
  display: grid; grid-template-columns: repeat(10, 30px); gap: 2px;
  max-height: 220px; overflow: auto;
  padding: 6px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--paper); box-shadow: 0 12px 26px rgba(0,0,0,.08);
}
.compose-emoji[hidden] { display: none; }
.compose-emoji button { font-size: 17px; min-width: 30px; padding: 0; }

.compose-form textarea {
  width: 100%; min-height: 240px; resize: vertical;
  border: 1px solid var(--line); border-radius: 10px;
  padding: 14px; font: inherit; color: var(--ink); background: var(--paper);
}
.compose-form textarea:focus { outline: none; border-color: var(--accent); }
.compose-files { font-family: var(--mono); font-size: 11px; color: var(--muted); margin: 8px 0 0; }
.compose-files[hidden] { display: none; }
.compose-send { margin-top: 16px; }

/* Filling the page stops short of the bar, so the site is still navigable. */
.compose.is-full {
  position: fixed; left: 12px; right: 12px; bottom: 12px;
  top: calc(var(--nav-h, 64px) + 12px);
  z-index: 40; display: flex; flex-direction: column; margin: 0;
}
.compose.is-full .compose-form { flex: 1 1 auto; overflow: auto; }
.compose.is-full textarea { flex: 1 1 auto; min-height: 200px; }
body.compose-full { overflow: hidden; }

.compose-sent {
  border: 1px solid var(--line); border-radius: 14px;
  background: var(--paper); padding: 28px 24px; text-align: center;
  box-shadow: 0 18px 40px rgba(0,0,0,.06);
}
.compose-sent[hidden] { display: none; }
.compose-sent p { margin: 0 0 18px; font-size: 18px; line-height: 1.7; }
.compose-sent span { font-family: var(--hand); font-size: 22px; color: var(--accent); }

/* Said only when the message genuinely did not get through. It appears just
   under the Send button — where the eye already is — rather than replacing the
   form, because everything typed is still there and the next thing to do is
   press Send again. */
.compose-problem {
  margin: 18px 0 0;
  border: 1px solid var(--accent); border-left-width: 4px;
  border-radius: 12px; background: var(--pastel);
  padding: 16px 20px;
}
.compose-problem[hidden] { display: none; }
.compose-problem p { margin: 0; max-width: 60ch; }
.compose-problem-head {
  font-family: var(--mono); font-size: 12px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--accent-ink);
}
.compose-problem-why { margin-top: 8px !important; font-size: 16px; line-height: 1.55; color: var(--ink); }
.compose-problem-why:empty { display: none; }
.compose-problem-next { margin-top: 8px !important; font-size: 15px; line-height: 1.55; color: var(--muted); }

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

/* The tools lie close together on the board, so one label was sitting over the
   next brush and catching its clicks. The clipped art is target enough. */
/* These five are clipped to a band, and a drop-shadow drawn inside that band
   gets cut off square — which is the faint rectangle around each of them. They
   lie flat on the page anyway, so they do without one. */
.obj[data-object="pen"] img, .obj[data-object="pencil"] img,
.obj[data-object="brush-fan"] img, .obj[data-object="brush-flat"] img,
.obj[data-object="brush-round"] img { filter: none; }

/* a single jog up and back down, for the two things that no longer turn */
@keyframes nudge {
  0%, 100% { translate: 0 0; }
  32%      { translate: 0 -13px; }
  66%      { translate: 0 4px; }
}
.obj[data-object="pen"].nudging, .obj[data-object="pencil"].nudging { animation: nudge .58s ease; }

.obj[data-object="pen"] .name, .obj[data-object="pencil"] .name,
.obj[data-object="brush-fan"] .name, .obj[data-object="brush-flat"] .name,
.obj[data-object="brush-round"] .name { pointer-events: none; }

/* On the board the tools sit in a tight stack on the page. Clipping them to a
   narrow band there left gaps where a click fell through to the page beneath,
   so on the board each tool is simply its own rectangle again, layered down the
   page in order. The clipping still applies on the desk, where the overlaps
   with the page underneath are the whole point. */
body.pm .obj[data-object="pencil"]      { z-index: 5; }
body.pm .obj[data-object="brush-round"] { z-index: 6; }
body.pm .obj[data-object="brush-fan"]   { z-index: 7; }
body.pm .obj[data-object="brush-flat"]  { z-index: 8; }
body.pm .obj[data-object="pen"]         { z-index: 9; }

.compose-tools svg { width: 15px; height: 15px; fill: currentColor; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; }
.compose-tools svg path { fill: none; }

/* ---------- contact: the editor and the envelope ---------- */
.compose-envelope {
  display: block; width: min(340px, 60%); height: auto;
  margin: 0 0 22px; opacity: .85;
}
.compose-editor {
  width: 100%; min-height: 240px; overflow: auto;
  border: 1px solid var(--line); border-radius: 10px;
  padding: 14px; font: inherit; color: var(--ink); background: var(--paper);
}
.compose-editor:focus { outline: none; border-color: var(--accent); }
.compose-editor:empty::before {
  content: attr(data-placeholder); color: var(--muted);
}
.compose-editor.is-missing { border-color: var(--accent); box-shadow: 0 0 0 3px var(--pastel); }
.compose-editor ul { margin: 0 0 0 18px; padding: 0; }
.compose.is-full .compose-editor { flex: 1 1 auto; min-height: 200px; }

/* The pen and the pencil are one picture each now, with no pose to match on,
   so the frame is simply shown. Without this they inherited the hidden state
   from the two-pose rules and disappeared under the page. */
.obj[data-object="pen"] .art img,
.obj[data-object="pencil"] .art img { opacity: 1; }

.compose-files.is-over { color: var(--accent); }

/* About: the words on the left, the photograph on the right, both finishing on
   the same line however long the text runs. */
.about-split {
  display: grid; gap: 40px; margin-top: 10px;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  align-items: start;
}
.about-words p:first-child { font-size: 20px; }
/* shown whole, never cropped, so the column simply takes the picture's shape */
.about-photo { margin-top: -34px; }
.about-photo img {
  display: block; width: 100%; height: auto;
  border-radius: 14px; border: 1px solid var(--line);
}
@media (max-width: 760px) {
  .about-split { grid-template-columns: 1fr; }

}

/* On the board these lie in a stack on the page, and a tall empty box was
   swallowing the click meant for the tool above it. Each is cut to the band
   it actually occupies, so every one answers for itself. */
body.pm .obj[data-object="pen"] .art { clip-path: inset(27.5% 0 38.8% 0); }
body.pm .obj[data-object="pencil"] .art { clip-path: inset(56.7% 0 3.5% 0); }
body.pm .obj[data-object="brush-fan"] .art { clip-path: inset(35.2% 0 39.6% 0); }
body.pm .obj[data-object="brush-flat"] .art { clip-path: inset(37.5% 0 40.2% 0); }
body.pm .obj[data-object="brush-round"] .art { clip-path: inset(37.7% 0 37.7% 0); }

.obj[data-object="brush-fan"].nudging, .obj[data-object="brush-flat"].nudging,
.obj[data-object="brush-round"].nudging { animation: nudge .58s ease; }

.compose-drop {
  align-self: flex-start; margin-top: 6px; padding: 3px 10px;
  border: 1px solid var(--line); border-radius: 999px; background: none;
  font-family: var(--mono); font-size: 11px; letter-spacing: .06em;
  color: var(--muted); cursor: pointer;
}
.compose-drop:hover, .compose-drop:focus-visible { color: var(--accent); border-color: var(--accent); }
.compose-drop[hidden] { display: none; }

/* Only the home page has two rooms to switch between, but the switch keeps its
   space on the others so the links never shuffle sideways between pages. */
body:not([data-page="home"]) .mode-toggle { visibility: hidden; pointer-events: none; }

/* At night the desk is dark but the torn page is still pale, so the labels of
   the things lying on it need the dark ink rather than the light one. */

/* In daylight the whole desk is pale, so every label takes the dark ink. */


/* the camera is drawn with room above and below it, so its label sits closer */
body:not(.pm) .obj[data-object="camera"] .art { margin-bottom: -52px; }
/* the scissors are drawn with room under them too */
body:not(.pm) .obj[data-object="scissors"] .art { margin-bottom: -34px; }

/* on the desk the round brush lies over the pen, in the order they were put down */
.obj[data-object="sticky-notes"] { z-index: 5; }
.obj[data-object="pencil"]       { z-index: 6; }
.obj[data-object="brush-round"]  { z-index: 8; }
.obj[data-object="pen"]          { z-index: 7; }

/* The mode switch belongs to the home page only, but it is now in the bar on
   every page and simply hidden on the others (just above). It therefore holds
   its own space, at every width, without a spacer having to guess at it — a
   guess that was 18px out between 941 and 1140px, and a whole row out on a
   phone, which is what made the bar jump as you moved between pages. */


/* And these are drawn on a canvas tall enough to hold them at an angle, so the
   label would otherwise sit far below the tool, over whatever is next to it. */
body:not(.pm) .obj[data-object="brush-round"] .art { margin-bottom: -40px; }
body:not(.pm) .obj[data-object="brush-fan"] .art   { margin-bottom: -62px; }
body:not(.pm) .obj[data-object="brush-flat"] .art  { margin-bottom: -30px; }
body:not(.pm) .obj[data-object="pen"] .art         { margin-bottom: -100px; }


/* ---------- art: two shelves ---------- */
.art-tabs { display: flex; gap: 6px; margin: 6px 0 26px; }
.art-tab {
  padding: 7px 16px; border: 1px solid var(--line); border-radius: 999px;
  background: none; color: var(--muted); cursor: pointer;
  font-family: var(--mono); font-size: 11px; letter-spacing: .08em;
  transition: color .2s ease, border-color .2s ease, background .2s ease;
}
.art-tab:hover, .art-tab:focus-visible { color: var(--accent); border-color: var(--accent); }
.art-tab.is-on { color: var(--ink); border-color: var(--accent); background: var(--pastel); }

/* A cross on a filter that is on, so it is obvious a click takes it off again
   rather than doing nothing. The cross is in the markup whether or not it shows
   and is only faded out, so the pill keeps exactly the same width in both
   states and the row never shuffles as you click along it. */
.art-tab { display: inline-flex; align-items: center; gap: 7px; }
.art-tab-x {
  font-size: 14px; line-height: 1; color: var(--accent);
  opacity: 0; transition: opacity .2s ease;
}
.art-tab.is-on .art-tab-x { opacity: 1; }
.art-group[hidden] { display: none; }
.art-group-title {
  font-family: var(--mono); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); margin: 0 0 30px;
}
/* The gap under the filters belongs to the shelf, not to its heading. It used
   to be set on the heading of whichever shelf sits next to the filters in the
   markup — which is always Paintings — so with Paintings switched off the
   Drawings heading lost 10px and the row jumped as you toggled. Setting it on
   every shelf means whichever one comes first sits in the same place. */
/* The shelf label sits square between the filters above it and whatever comes
   below — the same gap on each side, set in one place here and on the label's
   own bottom margin, so it reads as heading its shelf rather than being tucked
   under the filters. */
.art-group { margin-top: 30px; }
/* the larger gap belongs between two shelves that are both showing — a shelf
   that has been filtered away must not leave its spacing behind */
.art-group:not([hidden]) + .art-group { margin-top: 44px; }
.art-empty { color: var(--muted); font-family: var(--mono); font-size: 12px; letter-spacing: .05em; }

/* ---------- professional: the route here ----------
   Seven stops sharing the width of a column of text gave each one about 148px,
   which is not enough for "Team Leader, Project Management" — it came out three
   lines deep in 10px type. So the rail steps outside the text column and uses
   the width of the window, and below that it keeps its size and scrolls
   sideways instead of squeezing. */
/* The rail sits in the text column and fits it: no sideways scrolling, and no
   breaking out wider than everything above and below it. Seven stops share the
   column, so each has about 147px — the type in the cards is sized to that
   rather than the rail being sized to the type. */
.roadmap-scroll { margin: 26px 0 0; }
.roadmap-a { position: relative; height: 380px; margin: 0; }
/* the page styles list items with their own bullet and card, neither of which
   belongs on a station */
.roadmap-a .stops li::before, .roadmap-a .stops li::marker { content: none; display: none; }
.roadmap-a .stop, .roadmap-a .card {
  background: none; border: 0; box-shadow: none; padding: 0; margin: 0; border-radius: 0;
}
.roadmap-a .rail {
  position: absolute; left: 0; right: 0; top: 220px; height: 1px;
  background: repeating-linear-gradient(90deg, var(--line) 0 5px, transparent 5px 11px);
}
/* The bracket spans the four stops inside the LexisNexis years — the first four
   columns of seven — so it is set from the exact column edges rather than by
   eye, and stays put if the rail changes width. */
.roadmap-a .band {
  position: absolute; left: 0; width: 57.14%; top: 28px; height: 22px;
  border: 1px solid var(--line); border-bottom: 0; border-radius: 8px 8px 0 0;
}
.roadmap-a .bandlabel {
  position: absolute; left: 0; width: 57.14%; top: 6px; text-align: center;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .08em; color: var(--accent);
}
.roadmap-a .stops {
  position: absolute; left: 0; right: 0; top: 220px; margin: 0; padding: 0;
  list-style: none; display: grid; grid-template-columns: repeat(7, 1fr);
}
.roadmap-a .stop { position: relative; height: 0; }
.roadmap-a .dot {
  position: absolute; left: 50%; top: 0; translate: -50% -50%;
  width: 12px; height: 12px; border-radius: 999px;
  border: 1.5px solid var(--accent); background: var(--paper);
}
.roadmap-a .stop.role .dot { background: var(--accent); }
.roadmap-a .leg { position: absolute; left: 50%; width: 1px; background: var(--line); }
.roadmap-a .stop.role .leg  { bottom: 8px; height: 20px; }
.roadmap-a .stop.study .leg { top: 8px; height: 20px; }
/* The card is a share of its own stop rather than a fixed number of pixels, so
   it narrows with the column instead of growing over its neighbours when the
   window is smaller. A fixed 143px overlapped the next card below about 1000px. */
.roadmap-a .card {
  position: absolute; left: 50%; translate: -50% 0;
  width: calc(100% - 6px); padding: 0 3px; text-align: center;
}
.roadmap-a .stop.role .card  { bottom: 30px; }
.roadmap-a .stop.study .card { top: 30px; }
.roadmap-a .when { display: block; font-family: var(--mono); font-size: 10px; letter-spacing: .06em; color: var(--muted); }
.roadmap-a .what { display: block; font-size: 14.5px; line-height: 1.35; margin-top: 3px; }
.roadmap-a .where { display: block; font-size: 12px; color: var(--muted); margin-top: 2px; }
/* ---------- professional: a firmer roadmap ---------- */
.roadmap-a .rail {
  background: repeating-linear-gradient(90deg, var(--accent) 0 7px, transparent 7px 13px);
  opacity: .9; height: 2px;
}
.roadmap-a .dot { width: 15px; height: 15px; border-width: 2px; }
.roadmap-a .stop.role .dot { box-shadow: 0 0 0 4px var(--pastel); }
.roadmap-a .leg { width: 2px; background: var(--accent); opacity: .7; }
.roadmap-a .band {
  border: 2px solid var(--accent); border-bottom: 0; border-radius: 12px 12px 0 0;
  opacity: .75; height: 26px; top: 60px;
}
.roadmap-a .bandlabel {
  top: 30px; font-size: 12px; letter-spacing: .1em; color: var(--accent);
  text-transform: uppercase;
}
.roadmap-a .when { font-size: 10px; letter-spacing: .04em; color: var(--accent); white-space: nowrap; }
.roadmap-a .what { font-size: 13.5px; line-height: 1.3; }
.roadmap-a .where { font-size: 11.5px; }

/* ---------- professional: achievements ---------- */
.wins { list-style: none; margin: 20px 0 0; padding: 0; display: grid; gap: 14px; }
.wins li {
  position: relative; padding: 14px 18px 14px 46px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--paper);
  max-width: 78ch;
}
.wins li::before {
  content: ""; position: absolute; left: 18px; top: 22px;
  width: 10px; height: 10px; border-radius: 999px;
  background: var(--accent); box-shadow: 0 0 0 4px var(--pastel);
}
.wins li:hover { border-color: var(--accent); }

/* ---------- professional: tools that talk ----------
   Each chip has a line saying what the tool was actually used for. The line is
   anchored to the whole list rather than to its own chip, so it is always the
   same width as the list and can never run off the side of the screen — which
   the chips on the right-hand end used to do. The list keeps a gap under it for
   the line to appear in, so nothing below is covered and nothing moves. */
.tool-chips {
  position: relative;
  display: flex; flex-wrap: wrap; gap: 8px;
  list-style: none; padding: 0; margin: 18px 0 118px;
}
/* This list carries class="chips tool-chips", so it was also picking up the
   plain pill that .chips li draws for the qualification tags — a filled pill
   sitting behind the outlined button, and a pill inside a pill. With every chip
   already filled there was no room left to show which one you are on, so the
   outer one is turned off and the button is the pill. */
.tool-chips li { background: none; padding: 0; border-radius: 0; }

.tool-chips .chip {
  padding: 7px 15px; border: 1px solid var(--line); border-radius: 999px;
  background: none; color: var(--muted); cursor: pointer;
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .06em;
  transition: color .2s ease, border-color .2s ease, background .2s ease;
}
/* the chip itself answers, so it is clear which line you are reading */
.tool-chips li:hover .chip,
.tool-chips li:focus-within .chip,
.tool-chips li.is-open .chip {
  color: var(--ink); border-color: var(--accent); background: var(--pastel);
}

.tool-chips .chip-note {
  position: absolute; left: 0; right: 0; top: calc(100% + 14px); z-index: 5;
  padding: 14px 16px;
  border: 1px solid var(--accent); border-radius: 12px; background: var(--paper);
  box-shadow: 0 14px 30px rgba(0,0,0,.08);
  font-family: var(--body); font-size: 16px; line-height: 1.6; color: var(--muted);
  display: none;
}
/* one at a time: the chip you are on, the chip you have tabbed to, or the chip
   you tapped — the last of those is what makes this work without a mouse */
.tool-chips li:hover .chip-note,
.tool-chips li:focus-within .chip-note,
.tool-chips li.is-open .chip-note { display: block; }

/* The gap kept for the note would otherwise read as a hole in the page while
   nothing is chosen, and nothing would say the chips do anything. So it holds a
   line asking to be used, in the same place the note appears, and steps aside
   the moment one is. */
.tool-chips::after {
  content: "Hover a tool, or tap one, to see what it was used for.";
  position: absolute; left: 0; right: 0; top: calc(100% + 14px);
  padding: 14px 2px;
  font-family: var(--mono); font-size: 12px; letter-spacing: .05em;
  color: var(--muted); opacity: .75;
}
.tool-chips:has(li:hover)::after,
.tool-chips:has(li:focus-within)::after,
.tool-chips:has(li.is-open)::after { visibility: hidden; }

/* the narrower the list, the more lines a note wraps onto, so the gap kept for
   it grows to match — measured against the longest note at each width */
@media (max-width: 620px) {
  .tool-chips { margin-bottom: 150px; }
  .tool-chips .chip-note { font-size: 15px; }
}
@media (max-width: 400px) {
  .tool-chips { margin-bottom: 176px; }
}

/* ---------- professional: the case, in numbers, before anything else ---------- */
.proof {
  list-style: none; margin: 30px 0 0; padding: 0;
  display: grid; gap: 22px 30px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.proof li { border-top: 2px solid var(--accent); padding-top: 12px; }
.proof-n {
  display: block; font-family: var(--serif, Georgia, serif);
  /* a card is narrower than the old open column, and "1 week -> 2 days" is a
     lot longer than "98%" — so the figure is a step smaller and breaks at a
     sensible place rather than dropping one word onto a line of its own */
  font-size: clamp(27px, 3.2vw, 38px); line-height: 1.1; color: var(--ink);
}
.proof-t { display: block; margin-top: 8px; font-size: 13.5px; line-height: 1.5; color: var(--muted); }

.win-group + .win-group { margin-top: 30px; }
.win-title {
  font-family: var(--mono); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--accent); margin: 26px 0 10px;
}

/* One ink for the labels in each room's light, taken from the palette rather
   than plain black. The things lying on the torn page keep the dark one at
   night as well, because the page stays pale whatever the desk is doing. */
:root { --label: #5C3D48; }
[data-theme="night"] { --label: #D8C4CB; }
[data-theme="night"] .obj[data-object="sticky-notes"] .name,
[data-theme="night"] .obj[data-object="pencil"] .name,
[data-theme="night"] .obj[data-object="brush-round"] .name,
[data-theme="night"] .obj[data-object="pen"] .name,
[data-theme="night"] body.pm .obj[data-object="brush-fan"] .name,
[data-theme="night"] body.pm .obj[data-object="brush-flat"] .name { color: #5C3D48; }

/* On the board the tubes stand close together, so a long name wraps instead of
   running into the one beside it. */
body.pm .obj .name { max-width: 112px; }

/* 1: once paint is out, the tube's name sits on the puddle rather than under it */
.obj[data-object="paint-tube"].paint-1 .name,
.obj[data-object="paint-tube"].paint-2 .name,
.obj[data-object="paint-tube"].paint-3 .name {
  position: relative; z-index: 6; translate: -34px 16px;
}

/* 8: the sticky pad only lies on the torn page in the studio; on the board it
   sits on the dark surface and needs the light ink like everything else */
[data-theme="night"] body.pm .obj[data-object="sticky-notes"] .name { color: var(--label); }

/* 4: the gap under the filters is now set on .art-group itself, further up,
   so it does not depend on which shelf happens to be showing. */

/* ---------- professional: a mark beside each number ----------
   The four numbers sit on cards now, each one put down at a slightly different
   angle — the same idea as the things on the desk on the home page, kept small
   enough to read as paper rather than as a gimmick. The tilt is on the separate
   rotate property so the lift on hover can use transform without the two
   fighting, which is how the desk objects do it too. */
.proof li {
  display: grid; grid-template-columns: 34px 1fr; gap: 0 12px; align-items: start;
  background: var(--card);
  border: 1px solid var(--line); border-top: 2px solid var(--accent);
  border-radius: 12px;
  padding: 16px 18px 18px;
  box-shadow: 0 10px 22px var(--shadow);
  transition: transform .3s ease, box-shadow .3s ease, rotate .3s ease;
}
.proof li:nth-child(1) { rotate: -0.7deg; }
.proof li:nth-child(2) { rotate:  0.5deg; }
.proof li:nth-child(3) { rotate: -0.4deg; }
.proof li:nth-child(4) { rotate:  0.8deg; }
/* picking one up straightens it */
.proof li:hover {
  rotate: 0deg; transform: translateY(-4px);
  box-shadow: 0 18px 32px var(--shadow);
}
.proof-mark { margin-top: 4px; }
.proof-mark { grid-row: 1 / span 2; width: 30px; height: 30px; margin-top: 6px; }
.proof-mark svg { width: 30px; height: 30px; fill: none; stroke: var(--accent); stroke-width: 1.8;
  stroke-linecap: round; stroke-linejoin: round; }
.proof-mark .fill { fill: var(--pastel); stroke: none; }

/* ---------- professional: qualifications ----------
   Was a plain bordered list at reading size, which made seven short facts look
   like seven paragraphs. Same card language as the languages below it, with the
   year lifted out in the typewriter face so the list can be scanned by date. */
.quals {
  list-style: none; margin: 20px 0 0; padding: 0;
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  max-width: 1000px;
}
.quals li {
  border: 1px solid var(--line); border-radius: 12px;
  padding: 16px 18px; background: var(--paper);
  transition: border-color .2s ease;
  /* One qualification in a row can run to more lines than its neighbours, and
     the grid already stretches every card to the tallest. Making the card a
     column lets the certificate be pushed to the foot of it: titles stay at the
     top where they are read, and the pictures line up across the row instead of
     each stopping wherever its own text happened to end. */
  display: flex; flex-direction: column;
}
.quals li:hover { border-color: var(--accent); }
.quals .qual-year {
  display: block; font-family: var(--mono); font-size: 11px;
  letter-spacing: .08em; color: var(--accent);
}
.quals .qual-name { display: block; margin-top: 7px; font-size: 17px; line-height: 1.35; color: var(--ink); }
.quals .qual-where { display: block; margin-top: 5px; font-size: 13.5px; line-height: 1.45; color: var(--muted); }

/* ---------- a certificate under a qualification ----------
   A thumbnail rather than a link, so it is obvious there is an image to see,
   and a magnifier badge sits on it so it is equally obvious the image opens.
   Clicking goes through the same lightbox as the galleries. */
.cert { margin: auto 0 0; padding-top: 13px; }
.cert button {
  display: block; position: relative; width: 100%; padding: 0;
  border: 1px solid var(--line); border-radius: 9px; overflow: hidden;
  background: #FFFFFF; cursor: zoom-in; line-height: 0;
  transition: border-color .2s ease, box-shadow .25s ease, transform .25s ease;
}
.cert button:hover, .cert button:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 10px 22px var(--shadow);
  transform: translateY(-2px);
}
.cert img { display: block; width: 100%; height: 104px; object-fit: cover; object-position: center; }
/* two certificates belonging to one qualification, side by side in its card */
.cert-pair-row { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.cert-zoom {
  position: absolute; right: 7px; bottom: 7px;
  width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255, 255, 255, .93);
  box-shadow: 0 2px 7px rgba(42, 37, 40, .28);
  transition: background .2s ease;
}
.cert-zoom svg {
  width: 15px; height: 15px;
  fill: none; stroke: var(--accent-ink); stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round;
}
.cert button:hover .cert-zoom, .cert button:focus-visible .cert-zoom { background: #FFFFFF; }
.cert figcaption {
  font-family: var(--mono); font-size: 10px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted); margin-top: 7px;
}

/* ---------- professional: languages ---------- */
.langs { list-style: none; margin: 20px 0 0; padding: 0;
  display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); max-width: 780px; }
.langs li { border: 1px solid var(--line); border-radius: 12px; padding: 16px 18px; background: var(--paper); }
.langs .lang-name { display: block; font-size: 18px; }
.langs .lang-level { display: block; margin-top: 4px; font-family: var(--mono); font-size: 11px;
  letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
.langs .bar { display: block; margin-top: 12px; height: 5px; border-radius: 99px; background: var(--pastel); overflow: hidden; }
.langs .bar span { display: block; height: 100%; border-radius: 99px; background: var(--accent); }

/* 1 + 2: the name is set in the palette ink in daylight, not black */
[data-theme="day"] .brand, [data-theme="day"] .signature { color: #5C3D48; }

/* 10: a hovered object comes forward, and its label with it */
.obj:hover:not([data-object="paper"]),
.obj:focus-within:not([data-object="paper"]) { z-index: 30; }
.obj .name { position: relative; z-index: 31; }

/* 11 + 12: the about text is set exactly like the introduction on the home page */
.about-words p { font-size: 19px; color: var(--muted); }
.about-words p:first-child { font-size: 19px; }
.about-words p:first-child strong { color: var(--ink); }

/* Every page but the home page takes the about page's reading type, so the
   prose matches wherever you land. Headings, the small mono labels and the
   numbers keep their own jobs. */
body:not([data-page="home"]) .section > p:not(.eyebrow),
body:not([data-page="home"]) .about-words p,
body:not([data-page="home"]) .facts li,
body:not([data-page="home"]) .wins li,
body:not([data-page="home"]) .case p {
  font-family: var(--sans, "Karla", system-ui, sans-serif);
  font-size: 19px; line-height: 1.7; color: var(--muted);
}
/* .chip-note used to be in that list and came out at reading size, which is too
   big for a note in a panel — it sets its own size up with the chips instead. */

.footer-note {
  width: 100%; margin: 14px 0 0;
  font-family: var(--mono); font-size: 11px; letter-spacing: .05em;
  color: var(--muted); max-width: none;
}

/* ===========================================================
   Home page on a phone — a shorter desk
   Sixteen things stacked two-by-two made the home page enormous on a phone,
   and most of them are only really worth the tap with a pointer in your hand.
   So the small screen keeps the five that actually do something: the lamp,
   which lives in the hero and is untouched, and these four on the desk.
   Nothing here applies above 860px — the full desk is exactly as it was.
   =========================================================== */
@media (max-width: 860px) {


.about-split {
  display: flex; 
  flex-direction: column-reverse;
}

  /* everything except the four. Named the long way round rather than hiding
     twelve by name, so adding a new object to the desk does not silently add
     it to the phone as well. */
  .obj:not([data-object="tea-cup"]):not([data-object="camera"]):not([data-object="paint-tube"]):not([data-object="headphones"]) {
    display: none;
  }

  /* four things sit better two-by-two than three-and-then-one */
  .scene { grid-template-columns: repeat(2, 1fr); gap: 30px 18px; padding: 8px 0 0; }
  body.pm .scene {         
    height: 350px;
    padding: 20px 20px; 
  }

  /* with twelve gone there is room, so the four are bigger than they were */
  .obj img { max-height: 156px; }
  body.pm .obj img { max-height: 138px; }

  /* On the desk these pull the label back up under the drawing, because the
     pictures are on canvases tall enough to hold them at an angle. In the grid
     there is no tall canvas to allow for, so the pull was landing the label on
     top of the picture — the camera was the one that still showed. */
  .obj .art { margin-bottom: 0 !important; }

  .scene .name {
    display: none;
  }
  /* The pill was absolutely placed at a percentage of the page, and with the
     desk no longer positioned it measured that against the whole document —
     so it came out over the introduction, a screen and a half from the
     headphones, and hung off the right edge. Here it simply sits under the
     desk, and takes no room at all until there is something playing. */
  .player {
    display: none;
    position: static; transform: none;
    grid-column: 1 / -1; justify-self: center;
    margin-top: 2px;
  }
  body.music-on .player { display: inline-flex; }

  /* On the desk a label centres itself under its object with a half-width
     nudge, because the object is absolutely placed. In the grid the column
     already centres it, so the nudge only pushed it off — by 28px under the
     tea, which is why the names did not sit square under their pictures. */
  .obj .name { margin-left: 0; transform: none; }

  /* Same story for the shove that keeps the tube's name clear of the paint it
     has just squeezed out: on the desk the paint spreads sideways across the
     label, here it lands neatly underneath, so the shove is not wanted. */
  .obj[data-object="paint-tube"].paint-1 .name,
  .obj[data-object="paint-tube"].paint-2 .name,
  .obj[data-object="paint-tube"].paint-3 .name { translate: none; }
}

/* ===========================================================
   Case studies
   An index of cards, and one page per study so each has an address of its own
   that can go in an application. Every study runs the same six sections in the
   same order, so a reader learns the shape once and can skim the next one.
   Built entirely from the palette and the components already here — the number
   cards are the ones from the Professional page, the panels are the ones from
   Achievements.
   =========================================================== */

/* ---- the note explaining the shape ---- */
.case-shape {
  margin: 26px 0 0; padding: 14px 18px;
  border-left: 3px solid var(--accent); background: var(--paper-2);
  border-radius: 0 10px 10px 0;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.7;
  letter-spacing: .03em; color: var(--muted); max-width: 70ch;
}
.case-shape b { color: var(--ink); font-weight: 500; }

/* The line about what cannot be said. Set quieter than the shape note above it,
   because it is a caveat rather than an instruction for reading. */
.case-nda {
  margin: 16px 0 0; max-width: 70ch;
  font-size: 15.5px; line-height: 1.65; color: var(--muted);
}

/* ---- the index ---- */
.case-index { list-style: none; margin: 34px 0 0; padding: 0; display: grid; gap: 18px; }
.case-card { margin: 0; }
.case-card > a {
  display: grid; gap: 0 22px;
  grid-template-columns: 62px 1fr auto;
  grid-template-areas:
    "n kind  figure"
    "n title figure"
    "n sum   figure"
    "n go    figure";
  align-items: start;
  padding: 24px 26px;
  border: 1px solid var(--line); border-top: 2px solid var(--accent);
  border-radius: 14px; background: var(--card);
  text-decoration: none; color: inherit;
  box-shadow: 0 8px 20px var(--shadow);
  transition: transform .28s ease, box-shadow .28s ease, border-color .28s ease;
}
.case-card > a:hover, .case-card > a:focus-visible {
  transform: translateY(-3px);
  box-shadow: 0 18px 34px var(--shadow);
  border-color: var(--accent);
}
.case-card-n {
  grid-area: n; font-family: var(--mono); font-size: 30px; line-height: 1;
  color: var(--accent); opacity: .5;
}
.case-card-kind {
  grid-area: kind; font-family: var(--mono); font-size: 11px;
  letter-spacing: .1em; text-transform: uppercase; color: var(--accent);
}
.case-card-title {
  grid-area: title; margin-top: 8px;
  font-size: 23px; line-height: 1.25; color: var(--ink);
}
.case-card-sum {
  grid-area: sum; margin-top: 9px; max-width: 54ch;
  font-size: 16px; line-height: 1.6; color: var(--muted);
}
.case-card-go {
  grid-area: go; margin-top: 16px;
  font-family: var(--mono); font-size: 12px; letter-spacing: .07em; color: var(--accent-ink);
}
.case-card-go::after { content: " \2192"; transition: margin-left .25s ease; display: inline-block; }
.case-card > a:hover .case-card-go::after { margin-left: 5px; }
.case-card-figure {
  grid-area: figure; align-self: center; text-align: right;
  padding-left: 22px; border-left: 1px solid var(--line);
  min-width: 190px;
}
.case-card-figure b {
  display: block; font-family: var(--serif, Georgia, serif); font-weight: 400;
  font-size: 27px; line-height: 1.15; color: var(--ink);
}
.case-card-figure span {
  display: block; margin-top: 6px; font-size: 13px; line-height: 1.45; color: var(--muted);
}
@media (max-width: 780px) {
  .case-card > a {
    grid-template-columns: 46px 1fr;
    grid-template-areas: "n kind" "n title" "n sum" "n figure" "n go";
    padding: 20px 20px;
  }
  .case-card-figure {
    text-align: left; padding: 14px 0 0; margin-top: 14px;
    border-left: 0; border-top: 1px solid var(--line); min-width: 0;
  }
  .case-card-figure b { font-size: 23px; }
  .case-card-n { font-size: 24px; }
}

/* ---- a study ---- */
.case-back {
  font-family: var(--mono); font-size: 12px; letter-spacing: .07em;
  color: var(--muted); text-decoration: none;
}
.case-back:hover { color: var(--accent); }
.case-back::before { content: "\2190  "; }

.case-meta {
  margin: 14px 0 0; font-family: var(--mono); font-size: 12.5px;
  letter-spacing: .04em; color: var(--muted);
}

/* the number cards from the Professional page, doing the same job here. Capped
   so a study carrying only one card shows a card rather than a full-width band. */
.case-proof {
  margin-top: 30px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 300px));
  justify-content: start;
}

.case-body { margin-top: 46px; max-width: 74ch; }

.case-part + .case-part { margin-top: 40px; }
.case-part h2 {
  display: flex; align-items: center; gap: 12px;
  font-family: var(--mono); font-size: 12px; font-weight: 500;
  letter-spacing: .13em; text-transform: uppercase; color: var(--accent);
  margin: 0 0 14px;
}
.case-part h2::after {
  content: ""; flex: 1 1 auto; height: 1px; background: var(--line);
}
.case-part p { font-size: 17.5px; line-height: 1.75; color: var(--ink); max-width: 68ch; }
.case-part p + p { margin-top: 0; }

/* the one-liner at the top, set larger because it is the thing most people
   will read instead of the rest */
.case-lede p { font-size: 21px; line-height: 1.6; color: var(--ink); }

/* what was done, as steps rather than prose */
.case-steps { list-style: none; counter-reset: step; margin: 0; padding: 0; }
.case-steps li {
  counter-increment: step; position: relative;
  padding: 0 0 0 46px; margin: 0 0 18px;
  font-size: 17px; line-height: 1.7; color: var(--ink); max-width: 66ch;
}
.case-steps li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute; left: 0; top: 2px;
  font-family: var(--mono); font-size: 12px; letter-spacing: .06em;
  color: var(--accent);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 3px 8px; background: var(--paper);
}

/* The section people skip. It is given its own panel because it is the one
   that makes the rest believable. */
.case-lesson {
  border: 1px solid var(--line); border-left: 4px solid var(--accent);
  border-radius: 0 14px 14px 0; background: var(--paper-2);
  padding: 22px 26px;
}
.case-lesson h2 { margin-top: 0; }
.case-lesson h2::after { background: var(--line); opacity: .6; }

/* ---- before and after, as two bars ---- */
.case-ba { margin: 24px 0 0; padding: 0; }
.case-ba figcaption {
  font-family: var(--mono); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 16px;
}
.ba-row { display: grid; grid-template-columns: minmax(0, 210px) 1fr; gap: 14px; align-items: center; margin-bottom: 12px; }
.ba-label { font-size: 14px; line-height: 1.45; color: var(--muted); }
.ba-bar {
  position: relative; display: flex; align-items: center; justify-content: flex-end;
  height: 40px; width: var(--v, 50%); min-width: 64px;
  border-radius: 6px; background: var(--pastel);
  padding-right: 12px;
  transition: width .6s cubic-bezier(.2,.8,.2,1);
}
.ba-bar.is-after { background: var(--accent); }
.ba-bar b {
  font-family: var(--serif, Georgia, serif); font-weight: 400; font-size: 19px;
  color: var(--ink);
}
.ba-bar.is-after b { color: #fff; }
.ba-note { margin: 14px 0 0 !important; font-size: 14px !important; color: var(--muted) !important; }
@media (max-width: 620px) {
  .ba-row { grid-template-columns: 1fr; gap: 6px; }
  .ba-label { font-size: 13px; }
}

/* ---- the flow, before and after ---- */
.case-flow { margin: 24px 0 0; padding: 0; }
.case-flow figcaption {
  font-family: var(--mono); font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); margin-bottom: 14px;
}
.flow-line { margin-bottom: 22px; }
.flow-line > span {
  display: block; font-family: var(--mono); font-size: 11px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--accent); margin-bottom: 10px;
}
.flow-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: stretch; gap: 8px; }
.flow-steps li {
  flex: 1 1 130px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--paper);
  padding: 12px 14px; font-size: 14px; line-height: 1.4; color: var(--ink);
  position: relative;
}
.flow-line.is-after .flow-steps li { border-color: var(--accent); background: var(--pastel); }
.flow-steps li b {
  display: block; font-family: var(--mono); font-size: 10px; font-weight: 400;
  letter-spacing: .1em; color: var(--muted); margin-bottom: 5px;
}

/* ---- moving between studies ---- */
/* Previous, back to the list, next. Three columns are named explicitly rather
   than left to flow, because the first and last studies have only one
   neighbour - and with flow the middle link would slide sideways on those two
   pages instead of staying in the middle. */
.case-jump {
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  gap: 14px; margin-top: 56px; padding-top: 24px;
  border-top: 1px solid var(--line);
  font-family: var(--mono); font-size: 12.5px; letter-spacing: .05em;
}
.case-jump a { text-decoration: none; }
.case-jump .to-index { grid-column: 2; justify-self: center; color: var(--muted); }
.case-jump .to-index:hover { color: var(--accent); }

/* The arrows carry no words, so they are given a real target to press rather
   than a bare glyph, and an aria-label saying which study they lead to. */
.case-jump .case-prev, .case-jump .case-next {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; border-radius: 50%;
  border: 1px solid var(--line); color: var(--ink);
  font-size: 17px; line-height: 1;
  transition: border-color .2s ease, color .2s ease, transform .2s ease;
}
.case-jump .case-prev { grid-column: 1; justify-self: start; }
.case-jump .case-next { grid-column: 3; justify-self: end; }
.case-jump .case-prev:hover, .case-jump .case-prev:focus-visible,
.case-jump .case-next:hover, .case-jump .case-next:focus-visible {
  border-color: var(--accent); color: var(--accent);
}
.case-jump .case-prev:hover { transform: translateX(-2px); }
.case-jump .case-next:hover { transform: translateX(2px); }

/* ---- the parts still to be written ----
   Deliberately loud. These are prompts, not content — nothing in them is a
   claim about the work, and none of it should ever go live. Delete the strip
   and swap each block for real writing as you go. */
.case-draft-strip {
  display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: baseline;
  margin: 0 0 30px; padding: 14px 18px;
  border: 1px dashed var(--accent); border-radius: 10px;
  background: var(--pastel);
  font-family: var(--mono); font-size: 12px; letter-spacing: .04em; color: var(--accent-ink);
}
.case-draft-strip b { font-weight: 500; text-transform: uppercase; letter-spacing: .1em; }
.case-draft {
  border-left: 2px dashed var(--accent); padding-left: 16px;
  font-style: italic; color: var(--muted) !important;
  font-size: 16px !important; line-height: 1.7 !important;
}
.case-draft::before {
  content: "to write";
  display: block; font-style: normal; font-family: var(--mono);
  font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 6px;
}
/* inside the numbered steps the whole list is obviously unwritten, so the tag
   on every line is just noise — the dashed rule says it well enough */
.case-steps .case-draft { padding-left: 12px; margin: 0; }
.case-steps .case-draft::before { display: none; }
/* a prompt dropped mid-sentence, where part of the line is already real */
span.case-draft { display: inline; border: 0; padding: 0; }
span.case-draft::before { display: none; }

/* The three achievements that have a full write-up say so, and the role they
   all came from links across from the rail. */
.wins-more {
  display: inline-block; margin-left: 2px; white-space: nowrap;
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .06em;
  text-decoration: none; color: var(--accent-ink);
}
.wins-more::after { content: " \2192"; display: inline-block; transition: margin-left .25s ease; }
.wins-more:hover { color: var(--accent); }
.wins-more:hover::after { margin-left: 4px; }
.roadmap-a .what a { color: inherit; text-decoration: none; border-bottom: 1px solid var(--line); }
.roadmap-a .what a:hover { color: var(--accent); border-bottom-color: var(--accent); }

/* ===========================================================
   The journey on a phone
   The rail stands up rather than lying down: the same dashed line, the same
   dots (filled for a job, hollow for a course) and the same bracket round the
   LexisNexis years, all turned through ninety degrees with the dates and
   titles reading normally down the middle.

   The rail runs down the left and the bracket down the right, so the two
   never sit on top of each other, and the label is set on its side beside the
   bracket reading top to bottom.

   This sits at the end of the file on purpose. The "firmer roadmap" block
   above re-states height and background on .rail, and would otherwise flatten
   the standing rail back to a 2px sliver.

   The bracket cannot be one absolutely placed box here, because the stops are
   different heights once the titles wrap. It is drawn as a segment on each of
   the first four stops instead, which makes it exactly as tall as those four
   however they land.
   =========================================================== */
@media (max-width: 860px) {
  .roadmap-scroll { margin: 26px 0 0; padding: 0; overflow: visible; }
  .roadmap-a {
    height: auto; min-width: 0; max-width: none;
    padding: 6px 40px 0 44px;
  }
  .roadmap-a .band, .roadmap-a .leg { display: none; }

  /* the dashed line, now running down the page on the left */
  .roadmap-a .rail {
    left: 21px; right: auto; top: 10px; bottom: 12px;
    width: 2px; height: auto;
    background: repeating-linear-gradient(180deg, var(--accent) 0 7px, transparent 7px 13px);
    opacity: .9;
  }

  /* the years, set on their side down the right beside the bracket */
  .roadmap-a .bandlabel {
    position: absolute; right: 2px; top: 8px; left: auto;
    width: auto; text-align: left;
    writing-mode: vertical-rl;
    letter-spacing: .12em;
  }

  .roadmap-a .stops { position: static; display: block; }
  .roadmap-a .stop { height: auto; padding: 0 0 26px; }
  .roadmap-a .stop:last-child { padding-bottom: 0; }

  /* the dots sit on the line, level with the date they belong to */
  .roadmap-a .dot { left: -29.5px; top: 3px; translate: none; }

  .roadmap-a .card {
    position: static; translate: none; width: auto;
    padding: 0; text-align: left;
  }
  .roadmap-a .stop.role .card,
  .roadmap-a .stop.study .card { top: auto; bottom: auto; }

  /* the bracket down the right of the four LexisNexis years.
     height and border-radius are stated because the old .timeline component
     further up styles a round 10px bullet on .role::before, and .stop.role
     carries that class — an explicit height there stops top/bottom stretching
     these segments, which left the bracket in broken pieces. */
  .roadmap-a .stops li:nth-child(-n+4)::before {
    content: ""; display: block;
    position: absolute; right: -18px; left: auto; top: 0;
    /* Each stop draws its own length of the bracket, and the stops sit about
       3px apart - enough to leave the line visibly dashed. Every segment runs
       a few pixels past the foot of its stop so they overlap into one solid
       line; the last one is pulled back in below. */
    bottom: -4px;
    width: 2px; height: auto; border-radius: 0;
    background: var(--accent); opacity: .75;
  }
  .roadmap-a .stops li:nth-child(1)::before { top: 6px; }
  .roadmap-a .stops li:nth-child(4)::before { bottom: 20px; }
  /* the little turns at each end, pointing back in towards the entries */
  .roadmap-a .stops li:nth-child(1)::after,
  .roadmap-a .stops li:nth-child(4)::after {
    content: ""; position: absolute; right: -18px; left: auto;
    width: 9px; height: 2px; background: var(--accent); opacity: .75;
  }
  .roadmap-a .stops li:nth-child(1)::after { top: 6px; }
  .roadmap-a .stops li:nth-child(4)::after { bottom: 20px; }
}

/* Between the phone layout and the full-width rail, the seven stops are at
   their narrowest and a date like "Aug 2021 - Sep 2022" was the one thing that
   would not fit, breaking onto a second line. Held on one line and tightened
   just enough to sit inside its column instead of wrapping. */
@media (min-width: 861px) and (max-width: 1099px) {
  .roadmap-a .when { font-size: 9.5px; letter-spacing: 0; }
}

/* ---------- the white block behind the cut-outs on a phone ----------

   The desk objects put filter: drop-shadow inside an element that runs a
   transform animation, and iOS hands that pairing an opaque layer - which
   paints the picture's rectangle behind a shape that is not rectangular. The
   lamp proved it: same shadow, no animation, never a block.

   The first attempt at this turned the float off for touch, and fixed fourteen
   of the sixteen. Tea and headphones kept it, because they have animations of
   their own - a sip when you drink, a sway while music plays - declared later
   in this file and therefore out-ranking that rule. The sway is infinite, which
   is why the headphones never recovered. So the override belongs here, at the
   end, naming every animation that can reach .art.

   Desktop keeps all of it - the movement, the beat and the shadow. */
@media (hover: none) {
  .obj .art,
  .obj.wobble .art,
  .obj[data-object="tea-cup"].sipping .art,
  .obj[data-object="headphones"]:not([data-track="0"]) .art,
  .obj[data-object="paint-three"].shaking .art {
    animation: none;
    /* The beat was the piece I kept missing. While music plays, --beat is
       rewritten every frame and this scale follows it - on every object at
       once. That is why tapping the paint tube changed the block behind the
       teacup: one shared value re-rasterising sixteen layers together, and the
       block's strength moving with it. */
    scale: 1;
  }

  /* And the shadow goes. It is the common term in every version of this bug:
     a filter is what makes iOS composite the picture in the first place, and a
     composited transparent image is what gets handed the opaque backing. Four
     attempts kept it and four attempts left something behind; without it there
     is no filtered layer for the block to live in.

     The cost is real - the objects sit flatter on a phone, without the soft
     shadow that makes a cut-out look placed rather than pasted. Desktop keeps
     it in full. The shadows can come back on mobile by baking them into the
     picture files themselves, which needs no filter at render time. */
  .obj img,
  /* named the long way because the scoops carry a more specific filter of
     their own further up, and they only appear once the tube is squeezed -
     exactly the moment a block would show up */
  .obj[data-object="paint-tube"] .scoop,
  /* The big lamp has a drop-shadow of its own, which is why it was the last
     one still showing a faint rectangle - on a fresh load, before anything had
     forced a repaint. Tapping it cleared the block only because switching the
     light repaints the picture. Both states are named: the unlit one adds a
     brightness nudge on top of its shadow, and that is a filter too. */
  .lamp img,
  [data-theme="night"] .lamp .lamp-off { filter: none; }
}
