/* ============================================================
   Jack — interactive pixel portfolio
   ============================================================ */
:root{
  --ink:#eef2f7;
  --ink-dim:#9aa6b6;
  --ink-faint:#6b7788;
  --accent:#6fe3c4;
  --accent-2:#7fc9ff;
  --hair:rgba(255,255,255,0.10);
  --mono:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;
  --sans:'Inter',system-ui,-apple-system,'Segoe UI',sans-serif;
  /* the pixel face for anything that reads as part of the game's own UI */
  --pixel:'Pixelify Sans','JetBrains Mono',ui-monospace,monospace;
  /* and the coarser one, for the one thing that is signage rather than UI */
  --pixel-title:'Press Start 2P','Pixelify Sans',ui-monospace,monospace;
  /* The buttons are the exception to both. They are not scenery, they are the
     controls, and a control has to be read at a glance rather than decoded —
     which is exactly what a pixel face at 18px makes you do. Archivo is an
     ordinary grotesque: flat terminals, tall x-height, caps that are very nearly
     rectangles. Blocky enough to belong on a slab of pressed steel, plain enough
     to disappear into the label. Deliberately the least interesting type on the
     page. */
  --btn-type:'Archivo','Inter',system-ui,-apple-system,'Segoe UI',sans-serif;
  /* the metal the header buttons are pressed out of */
  --btn:#5b636f; --btn-hi:#848c99; --btn-lo:#2c313a; --btn-face:#e6ebf2;
}

*{box-sizing:border-box}
html,body{height:100%;margin:0}
body{
  background:#05070a;
  color:var(--ink);
  font-family:var(--sans);
  overflow:hidden;
  -webkit-font-smoothing:antialiased;
}

#stage{
  position:fixed;inset:0;
  width:100%;height:100%;
  display:block;
  image-rendering:pixelated;
  image-rendering:crisp-edges;
  cursor:default;
  touch-action:none;
}
#stage.hot{cursor:pointer}

/* ───────────────────────── loading ───────────────────────── */
#loading{
  position:fixed;inset:0;display:grid;place-items:center;
  background:#05070a;color:var(--ink-faint);
  font-family:var(--mono);font-size:12px;letter-spacing:.2em;
  z-index:100;transition:opacity .5s ease;
}
#loading.gone{opacity:0;pointer-events:none}

/* ───────────────────────── top bar ─────────────────────────
   No scrim and no blur any more. The bar used to be a translucent wash laid
   over the top of the scene, which is the one thing in here that looked like a
   web page: it dimmed the trusses behind it and it moved with nothing. What is
   there instead is hardware — each control is a slab of grey pressed out of the
   wall at the top of the warehouse, lit from above, sunk into its own recess
   with a shadow under it. Nothing is translucent, nothing is rounded, and the
   bevels are struck at whole pixels so they read as the same material as the
   scene rather than as CSS.

   The door was lowered thirty-eight base pixels to make the room for it — see
   HEADER_DROP in world.js — and the row is then set INTO that room rather than
   floated above it: app.js's fitHeader puts its top and its height in the
   coordinates of the strip of wall between the trusses and the head of the
   door, so the buttons are bolted to the building and travel with it when the
   scene is rescaled. The padding below is the horizontal one plus whatever
   this looks like for the one frame before that runs, and what a phone keeps. */
#topbar{
  position:fixed;left:0;right:0;z-index:30;
  top:var(--bar-top,28px);
  height:var(--bar-h,auto);
  display:flex;align-items:center;gap:14px;
  padding:0 44px;
  pointer-events:none;
  transition:top .42s cubic-bezier(.16,1,.3,1);
}
/* Pushed into a project, the wall the row is bolted to is not on screen any
   more — and what IS on screen is the framed object, whose top edge starts
   where the band used to end. So the row rides back up to the top of the
   window, on the same easing the push itself uses, and comes back down onto
   the wall when the wide shot returns. Same argument the name is faded out
   under: this is furniture of the wide shot, not chrome over everything.

   `height:auto` is the other half of it, and it is what halved the plate. At
   home the row's height is the height of the strip of wall it is set into
   (--bar-h, 83px at a maximised window) and the controls float in the middle
   of it, which put eleven pixels of air above the buttons and eleven below
   before the plate behind them even started. Off the wall there is no band to
   fill, so the row hugs its own tallest control and the whole assembly — row
   plus plate — comes down to `top` above and `top` below. See #valance. */
body.is-project:not(.is-mobile) #topbar{top:14px;height:auto}
#topbar > *{pointer-events:auto}

/* ───────────────────────── valance ─────────────────────────
   The buttons had nothing behind them in a close-up. In the wide shot they are
   pressed out of a named strip of warehouse wall and read as part of the
   building; travel up to top:14px over a close-up and the same eight slabs of
   grey are floating on a photograph of an aircraft, held up by nothing. The
   bevels say hardware and the context says overlay, and the two disagree.

   So the wall follows them. This is a pressed steel plate the width of the
   window that rolls down out of the top of the frame as the camera pushes in and
   parks the row on something — the head of a roll-up door, which is exactly the
   piece of building the row is bolted to when it is at home. It is the same
   metal: the gradient runs through --btn and the bevel is struck the same way,
   in whole pixels, so the buttons look milled out of it rather than laid on it.

   Behind, not over. z-index is under the header's and it never takes a pointer,
   so nothing about the controls changes — the plate arrives behind them.

   Timing is the row's, exactly: .42s on the same cubic-bezier the #topbar `top`
   transition uses, so the plate and the buttons travel as one piece of hardware
   instead of two things that happen to move at the same time. Off the top by
   101% rather than 100% — the drop shadow hangs below the box and a whole
   hundred per cent leaves a hairline of it across the top of the wide shot.

   ── Its size is now the row's size and nothing else, on all three sides.

   The height used to be the wall band plus thirty (113px at a maximised
   window), and the band is 83 for a row of 60-pixel controls: eleven pixels of
   air over the buttons, twenty-one under them, and a plate that ran a good way
   down over the top of the notebook. It is `2*top + control + bevel` instead —
   91px for the 60px links at top:14 with a 3px bevel ring — which is the same
   fourteen pixels of clear metal above the hardware as below it, and half of
   what was there. The steps at the breakpoints below are the same arithmetic
   with that breakpoint's control size and bevel.

   And it no longer starts at the left edge of the window. There was a third of
   a plate out there with nothing bolted to it, so `left` is set from where the
   first button actually is — see fitPlate in app.js, which measures the nav and
   sets --plate-l to fourteen pixels clear of it. Fourteen: the same number the
   top and the bottom margin come to, so the metal reads as one plate with an
   even reveal on the three sides you can see, rather than as a strip that
   happens to end somewhere. It gets a left edge to go with it — a lit inside
   column and a hard dark one outside, struck exactly like the top. */
#valance{
  position:fixed;left:var(--plate-l,0px);right:0;top:0;z-index:28;
  height:var(--plate-h,91px);
  pointer-events:none;
  transform:translateY(-101%);
  transition:transform .42s cubic-bezier(.16,1,.3,1);
  background:linear-gradient(to bottom,
    #7d8592 0,#6d7583 3px,
    #5b636f 38%,#525a65 62%,
    #454c56 84%,#343a43 93%,#22262d 100%);
  box-shadow:
    inset 0 3px 0 rgba(255,255,255,.15),
    inset 3px 0 0 rgba(255,255,255,.10),
    inset 0 -3px 0 rgba(0,0,0,.42),
    0 3px 0 0 #14171c,
    -3px 0 0 0 #14171c,
    0 6px 0 0 rgba(0,0,0,.5),
    -6px 0 0 0 rgba(0,0,0,.5),
    0 20px 28px -10px rgba(0,0,0,.6);
}
body.is-project:not(.is-mobile) #valance{transform:none}
/* Ribs pressed down the plate, on a 26px pitch. One lit column and one shadowed
   column per rib and nothing in between, at whole-pixel widths, so it reads as
   the same hard-edged material as the scene rather than as a soft CSS sheen. */
#valance::before{
  content:'';position:absolute;inset:0;
  background:repeating-linear-gradient(to right,
    rgba(255,255,255,.055) 0 1px,
    rgba(0,0,0,.11) 1px 2px,
    transparent 2px 26px);
}
/* And the bolt row along the bottom rail: 4px heads on a 44px pitch, lit on
   their top two rows and shadowed on their bottom two. Two flat background
   layers rather than a radial gradient — a round rivet is the one thing on this
   page that would give away that it was drawn by a stylesheet. */
#valance::after{
  content:'';position:absolute;left:0;right:0;bottom:7px;height:4px;
  background-image:
    repeating-linear-gradient(to right,rgba(255,255,255,.30) 0 4px,transparent 4px 44px),
    repeating-linear-gradient(to right,rgba(0,0,0,.48) 0 4px,transparent 4px 44px);
  background-size:100% 2px,100% 2px;
  background-position:14px 0,14px 2px;
  background-repeat:repeat-x,repeat-x;
}
/* A phone never leaves the close-up for a wide shot, so there is no wall for the
   row to be bolted to and nothing for this to stand in for. */
body.is-mobile #valance{display:none}

/* One industrial button: a lit top and left edge, a dark bottom and right, the
   dark ring of the recess it is set into, and a hard drop into the wall under
   it. All of it in box-shadow rather than borders, so the bevel can be three
   solid pixels without eating the padding — and driven off `--bev`, so the
   breakpoints can take it down with everything else instead of leaving a
   three-pixel chamfer on a thirty-six pixel button. */
#nav button,#navtoggle,.links a{
  --bev:3px;
  background:var(--btn);
  border:0;border-radius:0;
  color:var(--btn-face);
  text-shadow:0 2px 0 rgba(0,0,0,.75);
  box-shadow:
    inset 0 var(--bev) 0 var(--btn-hi),
    inset var(--bev) 0 0 rgba(255,255,255,.14),
    inset 0 calc(var(--bev) * -1) 0 var(--btn-lo),
    inset calc(var(--bev) * -1) 0 0 rgba(0,0,0,.32),
    0 0 0 var(--bev) #1b1f26,
    0 var(--bev) 0 var(--bev) rgba(0,0,0,.55);
  transition:background .12s,color .12s;
}
#nav button:hover,#navtoggle:hover,.links a:hover{background:#6c7480;color:#ffffff}
/* pressed: the light and the dark swap over and the whole slab drops into its
   recess, which is the only animation a button like this is allowed */
#nav button:active,#navtoggle:active,.links a:active{
  background:#4a515c;
  transform:translateY(var(--bev));
  box-shadow:
    inset 0 var(--bev) 0 var(--btn-lo),
    inset var(--bev) 0 0 rgba(0,0,0,.32),
    inset 0 calc(var(--bev) * -1) 0 rgba(255,255,255,.10),
    0 0 0 var(--bev) #1b1f26,
    0 1px 0 var(--bev) rgba(0,0,0,.55);
}

/* ── The menu control, small layouts only.
   Pressed out of the same metal as the four buttons it opens — it is one of the
   controls, not a piece of chrome over them, and it inherits the whole bevel
   from the rule above by being named in it. What is here is only its own layout:
   the label, the caret, and display:none, which is what keeps a desktop exactly
   as it was. The label is set by setNavCurrent in app.js and is the project you
   are in rather than the word MENU. */
#navtoggle{
  display:none;
  align-items:center;gap:12px;
  font-family:var(--btn-type);font-size:16px;font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;
  padding:12px 18px;cursor:pointer;
  white-space:nowrap;
}
/* A three-step staircase and not a border triangle: everything else on this page
   is drawn in whole pixels with hard edges, and a CSS triangle is a pair of
   anti-aliased diagonals. Three flat bands on a 3px grid — 5 cells, then 3, then
   1 — which is the same shape a pixel artist would draw and has no soft edge
   anywhere in it. Change the 3s together to resize it. */
.nt-caret{
  width:15px;height:9px;flex:none;
  background-image:
    linear-gradient(currentColor,currentColor),
    linear-gradient(currentColor,currentColor),
    linear-gradient(currentColor,currentColor);
  background-size:15px 3px,9px 3px,3px 3px;
  background-position:0 0,3px 3px,6px 6px;
  background-repeat:no-repeat;
  transition:transform .18s ease;
}
/* Open, it points at what it opened. A half turn of a shape built out of flat
   bands is exact — there is no diagonal in it to soften. */
body.nav-open .nt-caret{transform:rotate(180deg)}

/* The name is title art now, not a line of chrome: the given name large and
   bold over the surname, both in the pixel face. It is taken out of the flex
   row — at this size it would crush the nav — and app.js then sets it on the
   wall left of the doorway: same corner, same width as that wall, whatever the
   viewport does. The values here are only what it looks like for the one frame
   before that runs, and what a phone falls back to. */
/* Fixed, not absolute. It used to be absolute inside #topbar, which was fine
   while the bar sat at top:0 and its padding box started at the top of the
   window — the two coordinate systems agreed. Now that the bar is set down into
   the wall band, an absolute name inherits that drop and then adds its own on
   top of it, and the name lands a hundred pixels below where it was asked for.
   It is placed in window coordinates by app.js, so it should be positioned in
   them too. */
.brand{
  position:fixed;top:96px;left:34px;
  background:none;border:0;padding:0;margin:0;cursor:pointer;
  display:flex;flex-direction:column;align-items:flex-start;line-height:1;
  color:#ffffff;text-align:left;
}
/* Every line of the sign is white — see the three rules below. The surname and
   the discipline used to step down through --ink-dim, which is a hierarchy that
   works on a page and not on a wall: this is one piece of signage painted out of
   one tin, and the two greyer lines read as though the wall behind them was
   coming through the paint. Size already says which line is the name; it does
   not need saying twice. */
/* Set in the coarse pixel face, in capitals. It was in the same fine-grid face
   as the nav, which was right about the grid and wrong about the weight: at a
   foot high Pixelify Sans is a light face with rounded joins, and next to art
   whose blocks are three screen pixels across it read as type sitting on the
   picture rather than as something painted onto the wall. Press Start 2P is the
   same idea taken coarser — one stem per pixel on an eight-unit body, every
   stroke square, every terminal flat — which is the blocky title art the wall
   was asking for all along. Capitals for the same reason as before: the
   lower-case of any pixel face is where its grid shows worst.

   Tracking comes almost all the way off with it. The face is monospaced and
   already carries a full column of air inside each advance, so the .06em that
   was keeping Pixelify Sans's letters apart is now just width thrown away — and
   width is the one thing this face has none of to spare. What is left is a hair
   of it, to keep the two Ls in DRISCOLL from reading as one wide letter. */
.brand-first{
  font-family:var(--pixel-title);font-weight:400;font-size:60px;letter-spacing:.01em;
  text-transform:uppercase;
  color:#ffffff;
  text-shadow:0 5px 0 rgba(0,0,0,.92),0 0 34px rgba(111,227,196,.22);
  white-space:nowrap;
}
/* The whole name on one line — the small layout's version of the sign, and off
   the page entirely above the breakpoint. Every value is the surname's, because
   that is what it is: the bottom line of the two-line sign with the top line
   folded into it. It is `display:none` here rather than absent from the markup
   so that fitBrand has something to measure the instant the layout changes, and
   so neither version can be a different name from the other. */
.brand-full{
  display:none;
  font-family:var(--pixel-title);font-weight:400;font-size:20px;letter-spacing:.01em;
  text-transform:uppercase;
  color:#ffffff;
  text-shadow:0 3px 0 rgba(0,0,0,.9),0 0 26px rgba(111,227,196,.14);
  white-space:nowrap;
}
.brand-last{
  font-family:var(--pixel-title);font-weight:400;font-size:32px;letter-spacing:.01em;
  text-transform:uppercase;
  color:#ffffff;
  /* the descender room the em box leaves under the caps is what separates the
     two lines, so the surname gets the top margin the line box does not */
  margin-top:.42em;
  text-shadow:0 3px 0 rgba(0,0,0,.9),0 0 26px rgba(111,227,196,.14);
  white-space:nowrap;
}
/* The third line of the sign: what the name is a name for. Same face, same
   white, same tracking and the same flat drop as the surname — every value on
   this rule is now the surname's value except the size, which fitBrand sets off
   the width of the wall and which twenty-two letters of monospace will not let
   be the same (see the note there). It is the bottom line of the sign, not a
   caption under it, and nothing but the arithmetic is allowed to say otherwise;
   what separates the two lines is the margin instead.

   Its own tracking used to be .08em against the surname's .01, on the argument
   that this was the line most likely to close up into a bar of pixels. That was
   an argument for a line set two thirds the size of the one above it and a good
   deal lighter. Set at the surname's weight it does not need the air, and the
   air was being paid for out of the only budget this line is short of: a letter
   and a half of width, which is the difference between 14px and 16px. */
.brand-role{
  font-family:var(--pixel-title);font-weight:400;font-size:26px;letter-spacing:.01em;
  text-transform:uppercase;
  color:#ffffff;
  /* Set on two lines — app.js puts the break in, this renders it. pre-line and
     not <br> because the break is content: it collapses the runs of space the
     way any text does, and honours a newline written into `role` itself. */
  white-space:pre-line;
  line-height:1.32;
  /* Two whole lines of air, not a hair of it. The block under the name is now
     as tall as the name is, so the gap is the only thing saying the two are
     separate pieces of the sign — .42em separates the surname from the given
     name and this is nearly three times it. */
  margin-top:1.15em;
  /* Identical to the surname's, down to the numbers: same flat black drop, same
     turquoise bloom behind it. It used to be a 2px drop and a dimmer glow, which
     is the sort of thing that reads as a caption set in the same paint rather
     than as the bottom line of the same sign. */
  text-shadow:0 3px 0 rgba(0,0,0,.9),0 0 26px rgba(111,227,196,.14);
}
/* No hover state on the name. It used to go turquoise under the cursor, which
   made the one thing in the frame that is painted onto a wall behave like a
   link — signage does not light up when you point at it. It is still a button
   and still takes you back to the wide shot; the cursor is what says so. */
/* It is on the wall of the wide shot, so it goes when the wall does. Faded on
   the same sort of timing as the push rather than switched off at the top of it.
   The phone never has a wide shot to leave, so it keeps its name. */
.brand{transition:opacity .3s ease}
body.is-project:not(.is-mobile) .brand{opacity:0;pointer-events:none}

/* Hard right, not centred. The row used to carry an auto margin on both sides,
   which floated the four project buttons in the middle of the wall with a wide
   gutter either side of them: two groups of hardware bolted to the same strip
   with nothing between them, reading as two separate control panels. They are
   one panel. The nav takes the auto margin on the left only, and the bar's own
   gap is set to the same fourteen pixels the nav and the links space their
   buttons at — so Research Lab sits off LinkedIn by exactly what Joby
   Internship sits off Personal Projects by, and the six controls read as one
   run of switches from the far right of the wall inward. The gutter all of it leaves on the left is
   the point as well: that is where the conduit and the name live. */
#nav{display:flex;gap:14px;margin-left:auto}
/* Set in the plain grotesque rather than the pixel face — see --btn-type. Two
   things come off with the change. The size drops from 21px to 18px, because
   Archivo's caps are a good deal wider per letter than Pixelify Sans's and the
   controls have to stay inside the same run of wall; the sizes at every
   breakpoint below are stepped by the same ratio. And the tracking comes down
   from .06em to .04em: the extra column of air was there to stop a pixel face's
   stems running into each other, and a face with real side bearings does not
   need it.

   The label was run through a pixelating SVG filter for a while, and it is not
   any more. Quantising type onto a two-pixel grid costs exactly the thing the
   plain grotesque was chosen for: at nav sizes a cap is only six or seven cells
   tall, and six cells is not enough to keep an R and a B apart. */
#nav button{
  font-family:var(--btn-type);font-size:18px;font-weight:700;letter-spacing:.04em;
  text-transform:uppercase;
  padding:13px 22px;cursor:pointer;
  white-space:nowrap;
}
/* the one you are in stays pressed, and the lamp in it comes on */
#nav button[aria-current="true"]{
  color:#06110d;background:var(--accent);font-weight:700;
  text-shadow:none;
  box-shadow:
    inset 0 var(--bev) 0 rgba(255,255,255,.45),
    inset var(--bev) 0 0 rgba(255,255,255,.22),
    inset 0 calc(var(--bev) * -1) 0 rgba(0,0,0,.30),
    inset calc(var(--bev) * -1) 0 0 rgba(0,0,0,.18),
    0 0 0 var(--bev) #1b1f26,
    0 1px 0 var(--bev) rgba(0,0,0,.55),
    0 0 22px rgba(111,227,196,.35);
  transform:translateY(var(--bev));
}
#nav button[aria-current="true"]:hover{background:var(--accent);color:#06110d}

/* the links must never be the thing that gets clipped — they are the only way
   off the page, so they hold their size and the nav gives way first */
.links{display:flex;gap:14px;align-items:center;flex:0 0 auto}
.brand{flex:0 0 auto}
#nav{min-width:0}
.links a{
  display:grid;place-items:center;width:60px;height:60px;
}
.links a svg{width:30px;height:30px;fill:currentColor}

/* ───────────────────────── picture viewer ─────────────────────
   A print in a card, opened. It sits over everything including the header — a
   photograph at 80% of the window with a row of switches still bolted across
   the top of it would read as two pages at once — and it takes the pointer
   everywhere, so a click anywhere puts it back.

   80% of the SHORTER constraint in each direction, not 80% of both: the box is
   capped at 80vw and 80vh and the image keeps its own aspect inside that, so a
   wide photograph fills the width and a tall one fills the height, and neither
   is stretched. Nothing on this page is allowed to be a stretched photograph.

   `image-rendering:pixelated` because the art it is sitting on is, and a
   smoothly-resampled photograph in a pixel frame reads as a screenshot of a
   different website. */
#lightbox{
  position:fixed;inset:0;z-index:60;
  display:grid;place-items:center;
  background:rgba(8,6,3,.78);
  padding:4vmin;
  cursor:zoom-out;
  animation:lbIn .18s ease-out;
}
#lightbox[hidden]{display:none}
@keyframes lbIn{from{opacity:0}to{opacity:1}}
#lightbox figure{
  margin:0;display:flex;flex-direction:column;gap:10px;align-items:center;
  max-width:88vw;max-height:88vh;
}
/* The size is set in window pixels by sizeLightbox in app.js — CSS can cap a
   picture but cannot scale a small one up, and every photograph here is smaller
   than the box. These are the safety net and what one frame before that runs
   looks like. */
#lightbox img{
  display:block;
  max-width:88vw;max-height:88vh;
  width:auto;height:auto;
  image-rendering:pixelated;
  background:#f6ecd8;
  border:6px solid #d2bc93;border-radius:10px;
  box-shadow:0 10px 0 rgba(12,9,4,.34),0 30px 60px -20px rgba(0,0,0,.7);
}
#lightbox figcaption{
  font-family:var(--pixel);font-weight:700;font-size:17px;letter-spacing:.01em;
  color:#e4d5b4;text-align:center;
  max-width:80vw;
}
#lightbox figcaption:empty{display:none}
/* 1100 is MOBILE_MAX in app.js — the width the small layout starts at. The two
   have to agree and a stylesheet cannot read a constant out of a script, so it
   is written here as well; the note at MOBILE_MAX is the one that explains it. */
@media (max-width:1100px){
  #lightbox img{border-width:4px;border-radius:8px}
  #lightbox figcaption{font-size:14px}
}

/* ───────────────────────── tooltip ───────────────────────── */
#tooltip{
  position:fixed;z-index:25;pointer-events:none;
  font-family:var(--pixel);font-size:19px;font-weight:600;letter-spacing:.05em;
  color:#06110d;background:var(--accent);
  padding:6px 14px;border-radius:3px;
  transform:translate(-50%,-150%);
  white-space:nowrap;
  box-shadow:0 4px 18px rgba(0,0,0,.5);
}

/* ───────────────────────── chips ─────────────────────────── */
.chip{
  display:inline-flex;align-items:center;gap:7px;
  font-family:var(--pixel);font-size:15px;font-weight:500;letter-spacing:.03em;
  color:var(--ink);background:rgba(255,255,255,.06);
  border:1px solid var(--hair);border-radius:2px;padding:5px 11px;
  opacity:0;transform:translateY(6px);
  animation:chipIn .4s cubic-bezier(.16,1,.3,1) forwards;
}
.chip i{width:8px;height:8px;border-radius:1px;display:block}
@keyframes chipIn{to{opacity:1;transform:none}}

/* ───────────────────────── hint ──────────────────────────── */
#hint{
  position:fixed;left:50%;bottom:26px;transform:translateX(-50%);z-index:22;
  display:flex;align-items:center;gap:9px;
  font-family:var(--pixel);font-size:17px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;
  color:var(--ink-dim);
  background:rgba(5,7,10,.55);border:2px solid var(--hair);border-radius:3px;
  padding:10px 20px;
  transition:opacity .45s ease;
  pointer-events:none;
}
#hint.hide{opacity:0}
.pulse{width:7px;height:7px;border-radius:50%;background:var(--accent);animation:pp 1.9s ease-in-out infinite}
@keyframes pp{0%,100%{opacity:.25;transform:scale(.75)}50%{opacity:1;transform:scale(1.15)}}

/* ═════════════════════════ MOBILE ════════════════════════ */
#mobile{position:fixed;inset:0;z-index:26;pointer-events:none}
#mobile > *{pointer-events:auto}

#mdots{
  position:absolute;top:64px;left:0;right:0;
  display:flex;justify-content:center;gap:7px;
}
#mdots span{
  width:6px;height:6px;border-radius:50%;
  background:rgba(255,255,255,.25);transition:background .25s,width .25s;
}
#mdots span.on{background:var(--accent);width:20px;border-radius:3px}

#msheet{
  position:absolute;left:0;right:0;bottom:0;
  background:linear-gradient(to bottom,rgba(8,11,15,.72),rgba(8,11,15,.97));
  backdrop-filter:blur(12px);
  border-top:1px solid var(--hair);
  padding:8px 20px calc(20px + env(safe-area-inset-bottom));
  max-height:34vh;
  transition:max-height .45s cubic-bezier(.16,1,.3,1);
  display:flex;flex-direction:column;
}
#msheet.expanded{max-height:80vh}
#mgrab{
  background:none;border:0;padding:8px 0 10px;cursor:pointer;align-self:center;
}
#mgrab span{display:block;width:42px;height:4px;border-radius:2px;background:rgba(255,255,255,.28)}
.m-title{font-size:19px;font-weight:700;letter-spacing:-.01em}
.m-sub{font-family:var(--mono);font-size:11px;color:var(--ink-dim);margin-top:4px}
.m-scroll{overflow-y:auto;margin-top:12px;flex:1}
/* Shut, the sheet is a peek and not a scroller. Two reasons, and the second is
   the one that matters: scrolling a third of a screen of text a line at a time
   is not reading, and — since the write-up does not move under the thumb — every
   vertical drag on a shut sheet is unambiguously a drag OF the sheet. That is
   what lets the gesture in app.js follow the finger from anywhere on it rather
   than only from the grab handle. */
#msheet:not(.expanded) .m-scroll{overflow-y:hidden}
.m-body{font-size:14px;line-height:1.62;color:#cdd6e2}
.m-body p{margin:0 0 13px}
.m-body p:last-child{margin-bottom:0}

/* ── The deck's version of the cream column.
   Not the cream: the sheet is a dark drawer over a dark scene and a run of
   cream slabs in it would read as a different site. What carries across is the
   STRUCTURE — a header, a line under it, and a write-up that opens — because
   that is the part a phone was missing, not the paper. So each card is a hairline
   rule and a step of ground, which is what separates things on this sheet
   already. */
.m-card{border-top:1px solid var(--hair);padding:14px 0}
.m-card:first-child{border-top:0;padding-top:2px}
.m-card:last-child{padding-bottom:2px}

/* The header is a button when there is something behind it and a plain div when
   there is not — see mCard in app.js — so everything here has to hold for both,
   which is why the reset is on the class and not on the element. */
.m-card-head{
  display:grid;
  grid-template-columns:1fr auto;
  align-items:center;
  gap:2px 12px;
  width:100%;
  background:none;border:0;padding:0;margin:0;
  font:inherit;color:inherit;text-align:left;
  -webkit-tap-highlight-color:transparent;
}
button.m-card-head{cursor:pointer}
.m-card-title{
  grid-column:1;
  font-size:15px;font-weight:700;letter-spacing:-.01em;color:#eaf1f8;
  line-height:1.3;
}
.m-card-desc{
  grid-column:1;
  font-family:var(--mono);font-size:11px;line-height:1.45;
  color:var(--ink-dim);margin-top:3px;
}
/* The lead card is the one that names the whole thing, so it gets the step up
   in size the column gives it — and the column's other half of that promotion,
   the heavier outline, has no equivalent here and is not faked with a border. */
.m-card.is-lead .m-card-title{font-size:17px}
.m-card.is-lead .m-card-desc{color:var(--ink-dim)}

/* The same three-step staircase the nav caret is drawn out of, at two thirds the
   size and turned a quarter — flat bands on a 2px grid, so there is no
   anti-aliased diagonal anywhere in it and it sits beside pixel type without
   arguing with it. It spans both description and title rows, so it centres on
   the block rather than on the first line of it. */
.m-card-chev{
  grid-column:2;grid-row:1 / span 2;
  align-self:center;
  width:10px;height:6px;flex:none;
  background-image:
    linear-gradient(currentColor,currentColor),
    linear-gradient(currentColor,currentColor),
    linear-gradient(currentColor,currentColor);
  background-size:10px 2px,6px 2px,2px 2px;
  background-position:0 0,2px 2px,4px 4px;
  background-repeat:no-repeat;
  color:var(--ink-faint);
  transition:transform .18s ease,color .18s;
}
.m-card.is-open .m-card-chev{transform:rotate(180deg);color:var(--accent)}
.m-card-body{margin-top:12px}

/* A link in the copy, and the same blue the cards strike one in. Underlined
   rather than coloured alone: this is a dark ground and colour by itself is the
   first thing a phone screen in daylight loses. */
.m-link{
  color:var(--accent);
  text-decoration:underline;
  text-underline-offset:3px;
  text-decoration-thickness:1px;
  overflow-wrap:anywhere;
}
/* A print in the sheet. Framed in the cream and the chunky darker-cream outline
   the cards and the picture viewer use, so it reads as the same photograph
   printed on the same paper rather than as an image in a web page — and as wide
   as the text column, because that is the one measurement a column of writing
   has. Tap it and it opens in the viewer, like the prints in a card do. */
.m-shot{margin:0 0 15px}
.m-shot img{
  display:block;width:100%;height:auto;
  image-rendering:pixelated;
  background:#f6ecd8;
  border:4px solid #d2bc93;
  cursor:pointer;
}
.m-shot figcaption{
  font-family:var(--mono);font-size:11px;color:var(--ink-dim);
  margin-top:7px;
}
.m-stack{display:flex;flex-wrap:wrap;gap:6px;margin:14px 0 4px}

/* ── The sheet's copy, swapped.
   When a swipe changes project the picture behind slides (see drawMobile) and
   the writing under it used to simply be different on the next frame. Two
   different things happening to two halves of one screen reads as a redraw
   rather than as a move, so the copy travels too — the same direction, a shorter
   distance, and a little behind, which is the ordinary trick for making two
   objects at different depths look like they are in the same shot.

   `both` on the fill so the start frame applies before the first tick: without
   it the text flashes at its final position for one frame and then jumps back to
   start the animation. Retriggered from JS by removing the class, forcing a
   reflow and adding it again — an animation only restarts on a fresh element or
   a fresh class. */
@keyframes m-swap-l{from{opacity:0;transform:translateX(-26px)}to{opacity:1;transform:none}}
@keyframes m-swap-r{from{opacity:0;transform:translateX( 26px)}to{opacity:1;transform:none}}
.m-swap-l{animation:m-swap-l .34s cubic-bezier(.16,1,.3,1) both}
.m-swap-r{animation:m-swap-r .34s cubic-bezier(.16,1,.3,1) both}
/* A finger already on the sheet is dragging the sheet, and a transform running
   under it fights that. Nothing animates while a drag is live. */
#msheet.is-dragging .m-swap-l,#msheet.is-dragging .m-swap-r{animation:none}

/* ── While a finger is on it.
   The sheet follows the thumb, so the .45s ease that opens and shuts it has to
   be off for the duration or every frame of the drag is chasing a transition
   that is chasing the finger. Put back the moment the finger lifts, which is
   what makes the release settle rather than stop. */
#msheet.is-dragging{transition:none}

#mswipe{
  position:absolute;left:50%;transform:translateX(-50%);
  bottom:calc(34vh + 18px);
  font-family:var(--mono);font-size:10px;letter-spacing:.3em;text-transform:uppercase;
  color:var(--ink-faint);transition:opacity .4s;
}
#mswipe.hide{opacity:0}
/* And it goes when the sheet comes up over it. It is parked 18 above where the
   SHUT sheet ends, so an expanded one is drawn straight over the word and you
   get SWIPE in grey capitals in the middle of a paragraph. The sibling selector
   works because #mswipe follows #msheet in the markup. */
#msheet.expanded ~ #mswipe{opacity:0}

/* Four project buttons and two marks at double size need about 1560px of room.
   Below that everything steps down together — measured, not guessed: at the
   previous values the row still came to the full 1418px of a maximised window
   and ran off the right edge. Kept at the width the row needed when it also
   carried a résumé slab and a mail icon: the two that went are the reason there
   is now air to the left of it rather than a reason to redraw the steps. */
@media (min-width:1101px) and (max-width:1559px){
  /* the bar's gap tracks the nav's at every step, or the seam between Research
     Lab and LinkedIn opens up again as soon as the row steps down */
  #topbar{padding:0 26px;gap:10px}
  #nav{gap:10px}
  #nav button,.links a{--bev:2px}
  #nav button{font-size:13px;padding:10px 14px}
  .links{gap:10px}
  .links a{width:42px;height:42px}
  .links a svg{width:21px;height:21px}
  /* 2*14 + 42 + 2 — see #valance */
  #valance{--plate-h:72px}
}
/* There used to be a third tier under this one — a "narrow desktop" step at
   821-1100 that took the buttons down to 12px so the row would still fit beside
   the sign. It is gone, and not because the numbers were wrong: everything in
   that band is the small layout now (see MOBILE_MAX in app.js), the nav is a
   menu there and the valance is not drawn at all, so every rule in it addressed
   an element that is no longer on screen at that width. The step from the
   desktop row to the menu is the breakpoint itself now. */

/* ═══════════════════ THE SMALL LAYOUT'S HEADER ═══════════════════
   Under the breakpoint there is no wall to bolt anything to — the deck is a
   close-up of one object with a sheet of writing under it, and it fills the
   window. So the header stops being hardware set into the scene and becomes
   what it has to be there: a block in the top-left corner that is on screen at
   every moment, whatever you are looking at.

   Three lines, in this order and always all three:

       JACK DRISCOLL-NATALE      the whole name, one line, fitted to the width
       ELECTRICAL ENGINEERING    the discipline under it, one line
       [ JOBY INTERNSHIP  ▾ ]    the menu, which is the four project buttons

   It is laid out as a grid rather than as three absolutely positioned pieces,
   which is what it was: the name is measured and set by fitBrand at every width
   now (the breakpoint reaches 1100, so a fixed size cannot serve both ends of
   that), and a stack of hand-placed `top` values cannot follow a line whose
   height it does not know.

       ┌─────────────────────────────┐
       │ sign  (name + role)         │
       ├──────────────────────┬──────┤
       │ menu                 │ link │
       └──────────────────────┴──────┘

   The sign takes a whole row of its own and the LinkedIn slab drops to the
   menu's, which is worth the one line it costs: twenty capitals of a monospaced
   face is the widest single thing on the page, and beside a 40px slab on a
   390-point screen it was being set at fifteen — smaller than the SURNAME alone
   used to be when the name was on two lines. With the row to itself it gets the
   whole 358 and comes out at seventeen. The two slabs end up on one row with the
   air between them, which is the arrangement the desktop header has anyway. */
/* ── And it is bolted to a plate, like the desktop row is.
   The same pressed steel #valance is milled from, and for the same reason: the
   name, the menu slab and the LinkedIn mark are hardware, and hardware sits on
   something. Floating over the blurred scene they took their background from
   whatever project was up — brown over the beach, teal over the channel — so the
   one fixed piece of the page was the piece that never looked the same twice.

   The gradient is the valance's, unchanged; what goes is its left and right
   inset highlights, which model a plate that stops short of the left edge. This
   one runs the full width, so an edge light down the sides would be lighting a
   seam that is not there. It keeps the top highlight, the dark underside and the
   two hard steps below it, which is what makes it read as a slab with a
   thickness rather than as a colour behind some text.

   No transform and no transition: the valance ROLLS DOWN when you push into a
   project, because on a desktop the buttons move from the warehouse wall onto
   the plate. There is no wall here and nothing travels — the header is the same
   fixture on every screen of the deck. */
body.is-mobile #topbar{
  top:0;height:auto;
  /* bottom padding where there was none: the plate now has an underside, and
     the menu slab was sitting on it. --mhdr is measured off this element in
     fitBrand, so the dots and the picture under them follow it down by
     themselves. */
  padding:14px 16px 16px;
  background:linear-gradient(to bottom,
    #7d8592 0,#6d7583 3px,
    #5b636f 38%,#525a65 62%,
    #454c56 84%,#343a43 93%,#22262d 100%);
  box-shadow:
    inset 0 3px 0 rgba(255,255,255,.15),
    inset 0 -3px 0 rgba(0,0,0,.42),
    0 3px 0 0 #14171c,
    0 6px 0 0 rgba(0,0,0,.5),
    0 20px 28px -10px rgba(0,0,0,.6);
  display:grid;
  /* minmax(0,1fr) and not 1fr: the name is one nowrap line of a monospaced face,
     and a bare 1fr takes its minimum from its content, so a name too wide for
     the window would push its own column out and take the LinkedIn slab off the
     side of the screen. Floored at zero, the column is what is left after the
     slab and the name is fitted into it — which is the measurement fitBrand
     makes, so the two agree by construction. */
  grid-template-columns:minmax(0,1fr) auto;
  grid-template-areas:"sign sign" "menu link";
  align-items:start;
  gap:12px 14px;
}
body.is-mobile #hint,body.is-mobile #tooltip{display:none!important}
/* Back in the flow, and in the grid. On the wide shot the sign is `position:
   fixed` and app.js puts it on the wall in window coordinates; here there is no
   wall and it is simply the first cell of the header. The inline top/left
   fitBrand sets for the wall case are cleared by fitBrand itself when the layout
   changes, so nothing of the other mode is left behind. */
body.is-mobile .brand{position:static;grid-area:sign;min-width:0}
/* Beside the menu, hard right, and centred on it — two slabs of the same metal
   on one rail. The auto margin that pushed it right on the wide shot comes off:
   the grid is what puts it there now, and leaving both in would fight. */
body.is-mobile .links{grid-area:link;margin-left:0;align-self:center;justify-self:end}
body.is-mobile #navtoggle{grid-area:menu;display:inline-flex;justify-self:start}

/* One line, not two. The given name over the surname is what makes the wall
   version read as painted signage, and it is exactly the wrong shape here: this
   block has a third line under it now and a two-storey name would push the menu
   into the middle of the picture. So the pair is swapped for the whole name at
   one size — see .brand-full, which is where all of its styling is. */
body.is-mobile .brand-first,body.is-mobile .brand-last{display:none}
body.is-mobile .brand-full{display:block}

/* ── Heavier, and by the one method a pixel face allows.
   Press Start 2P ships a single weight. `font-weight:700` on it gets you the
   browser's synthetic bold, which smears the glyph horizontally by a fraction
   of a pixel and turns a face whose entire premise is square stems into a blurry
   one — the exact failure the rest of this stylesheet is built to avoid.

   So the ink is added the way the canvas type adds it: PX.text's `weight` is n
   grid cells of extra ink to the RIGHT of every stamp and not a hair of extra
   advance, and a text-shadow at a whole-pixel x offset is precisely that. Every
   stem gains a pixel on one side, nothing re-measures, nothing re-wraps, and
   fitBrand's sizing is untouched because the advance never changed.

   Both existing shadows come along unaltered — the flat black drop and the
   turquoise bloom — because a shadow list replaces rather than adds to. The new
   copy goes FIRST so the black drop paints over its right edge rather than under
   it, which is what keeps the thickened stem reading as one solid stroke. */
body.is-mobile .brand-full,
body.is-mobile .brand-role{
  text-shadow:
    1px 0 0 currentColor,
    0 3px 0 rgba(0,0,0,.9),
    0 0 26px rgba(111,227,196,.14);
}
/* And the discipline comes back onto one line with it. roleLines() writes a
   newline into the middle of it for the wall, where the sign is a block of title
   art and two short lines beat one long one; `nowrap` collapses that newline to
   a space, so the copy in content.js does not have to know which layout it is
   in. The lead-in is the surname's .42em rather than the sign's 1.15em: two
   whole lines of air is what separates the sign from its subtitle on a wall, and
   here the menu slab under it is doing that job. */
body.is-mobile .brand-role{white-space:nowrap;margin-top:.42em}

/* ── The menu, shut and open.
   Shut it is one slab; open it is the same four buttons the desktop row is made
   of, dealt down the left edge under it. Absolutely positioned off the header
   rather than in it, for one reason: the dots and the picture under them are
   placed off the header's height, and a menu that pushed the whole deck down
   the screen every time it opened would be the tail wagging the dog. It arrives
   OVER the scene, like the picture viewer does. */
body.is-mobile #nav{
  display:none;
  position:absolute;top:calc(100% + 10px);left:16px;
  margin-left:0;
  flex-direction:column;align-items:stretch;
  gap:8px;
  min-width:230px;max-width:calc(100vw - 32px);
}
body.is-mobile.nav-open #nav{display:flex}
/* Left, not centred: they are a list of places and the eye reads the first
   letter of each down one edge. Bigger targets than the desktop row's, too —
   this is the one layout where a thumb is doing the pointing. */
body.is-mobile #nav button{
  font-size:15px;padding:14px 16px;text-align:left;
  --bev:3px;
}
/* the one you are in is already lit by the shared aria-current rule; nothing
   here needs to say it twice */

/* Under the whole block, and measured rather than counted. --mhdr is the header
   row's real height, published by fitBrand every time it sizes the sign — the
   name, the discipline and the menu slab are all set from the width now, so
   there is no arithmetic anybody could do here that would stay true. The
   fallback is what a 390-point screen comes to, for the one frame before the
   first measurement lands. */
body.is-mobile #mdots{top:calc(var(--mhdr,150px) + 14px)}
body.is-mobile .links{gap:10px}
body.is-mobile .links a{width:40px;height:40px;--bev:2px}
body.is-mobile .links a svg{width:20px;height:20px}
/* SWIPE is an instruction to a finger. Below the breakpoint there is now a great
   deal of hardware that has a pointer and no touchscreen — every laptop with a
   narrow window — and on those the menu and the arrow keys are the navigation,
   so the word is simply wrong. `hover:hover` is the closest thing CSS has to
   "there is a pointer here"; a phone reports `hover:none` and keeps it. */
@media (hover:hover){#mswipe{display:none}}



/* ═══════════════════ THE CREAM CARDS — per-card sizes ═══════════════════
   The column of cream cards is drawn on the canvas, not built out of DOM, so
   there is nothing there for a stylesheet to reach. What is there instead is a
   set of probe elements — one per card, hidden, zero by zero, never drawn —
   built by stack.js and read back off the cascade. You write ordinary rules
   about them and the panel is drawn to match. See CSS OVERRIDES in
   js/scenes/stack.js for the whole of how it works.

   ── The four things you can set, and the ID to set them on.

       #card-<project>-<n>                  height     the card, shut
       #card-<project>-<n> .pcard-title     font-size  the header
       #card-<project>-<n> .pcard-desc      font-size  the line under it
       #card-<project>-<n> .pcard-body      font-size  the write-up inside

   `<project>` is the project's `id` in content.js — there are four:

       workstation   water-monitor   drone   evtol

   `<n>` counts that project's `sheets` from 1, in the order they are written in
   content.js. The card at the top of the column that names the whole project —
   `lead` in content.js — is `-lead` rather than `-1`, so adding or taking one
   away does not renumber every card under it.

       #card-evtol-lead    the Joby title card
       #card-evtol-1       Fault Injection Simulator
       #card-evtol-2       Partial Discharge Risk Tool
       #card-evtol-3       FAA-facing Test Coupon
       #card-evtol-4       Hardware-In-The-Loop Testing
       #card-evtol-5       Radar Eval Board

   `workstation` has six sheets and no lead card, so it is 1 to 6.
   `water-monitor` has a lead and four sheets, `drone` a lead and three.

   ── Two things about the numbers.

   They are in the PANEL's pixels, which are twice the scene's, and they are the
   numbers stack.js is written in. As drawn today: a header is 28, 21 or 14
   depending on how many cards the project has to fit, a description is 14, a
   write-up is 14, and a card is somewhere between 76 and 140 tall. They are not
   window pixels — the whole scene is scaled to the window — so a card set to
   120 is 120 of the same pixels the automatic pass measures in, at every window
   size. Which is the point: set it once and it holds.

   And a font-size SNAPS to the nearest size the pixel face can actually be
   drawn at. Those are the multiples of seven:

       7   14   21   28   35   42   49   56

   Anything else is resampled letterforms, which is the one thing this panel is
   not allowed to be — so ask for 25 and you get 28. Heights are not snapped
   like this; they are taken as written, rounded to an even number.

   ── What happens to the cards you do NOT mention.

   Nothing, and that is the whole design of it. Every property below defaults to
   zero, stack.js reads zero as "not asked for", and a card nobody has said
   anything about comes out at exactly the size it does today — its height
   divided out of the frame with the others, its header at the largest of the
   three sizes that fits every title in the project.

   Give one card a height and it is taken out of that divide: it gets what you
   asked for, and what is left of the frame is shared among the rest. Give one
   card a header size and it is taken out of the ladder as well, so a card set
   large cannot drag the other titles down a size to make room for itself.

   Set enough of them tall enough and the column is longer than the frame, at
   which point it scrolls, which it already knows how to do. Nothing breaks; you
   just lose the "all of it on one screen" property the panel is built around.

   The one combination to watch is a header set larger than its card is tall.
   The type block is centred in the card and then clipped to it, so a 42 header
   in a 76-pixel card pushes the description off the bottom edge rather than
   shrinking to fit — the size you asked for is the size you get. If a title
   comes out cut short with an ellipsis instead, that is the other limit: it is
   wider than the column, and the fix is fewer words or a size down.

   ── Checking it took. STACK.debugRows() in the console prints every card's
      element ID next to the four numbers it came out at, which is the quick way
      to tell a size that snapped to a different rung from a selector that never
      matched — on a screenshot the two look the same.

   ── Editing this live. Change a rule in the browser's own inspector, then run
      STACK.restyle() in the console — the cards are baked bitmaps and that is
      what rebuilds them. A reload does it too. */

/* The plumbing. Zero specificity on purpose — every selector is wrapped in
   :where() — so any rule you write below wins on specificity alone and the
   order of the two blocks never matters. Do not edit this one. */
:where(#card-styles){
  position:fixed;top:0;left:0;width:0;height:0;
  overflow:hidden;visibility:hidden;pointer-events:none;
}
/* Not display:none: a box that generates no layout reports its height back as
   `auto` rather than as a number, and a number is the whole point. */
:where(#card-styles .pcard){height:0}
:where(#card-styles .pcard > span){display:block;font-size:0}

/* ─────────────────────────────────────────────────────────────────────────
   YOUR RULES GO HERE. Everything below is commented out — uncomment what you
   want and change the numbers. These examples are the current values written
   out explicitly, so uncommenting one on its own changes nothing; they are
   there to be edited.
   ───────────────────────────────────────────────────────────────────────── */

/* One card, all four properties.

#card-evtol-1{ height:96px }
#card-evtol-1 .pcard-title{ font-size:28px }
#card-evtol-1 .pcard-desc { font-size:14px }
#card-evtol-1 .pcard-body { font-size:14px }
*/

/* A whole project's headers a size down, in one rule.

#card-workstation-1 .pcard-title,
#card-workstation-2 .pcard-title,
#card-workstation-3 .pcard-title,
#card-workstation-4 .pcard-title,
#card-workstation-5 .pcard-title,
#card-workstation-6 .pcard-title{ font-size:14px }
*/
#card-water-monitor-lead .pcard-title{
  font-size:21px;
}

#card-water-monitor-lead {
  height:80px;
}

#card-water-monitor-1 .pcard-title{
  font-size:14px;
}

#card-water-monitor-1 {
  height:75px;
}

#card-water-monitor-2 {
  height:75px;
}

#card-water-monitor-3 {
  height:75px;
}

#card-water-monitor-4 {
  height:75px;
}

#card-water-monitor-5 {
  height:75px;
}

#card-water-monitor-2 .pcard-title{
  font-size:14px;
}

#card-drone-lead .pcard-title{
  font-size:21px;
}

#card-drone-1 {
  height:80px;
}

#card-drone-2 {
  height:80px;
}

#card-drone-3 {
  height:80px;
}



/* The lead card taller and its title bigger — it is the one card whose height
   is normally decided by how many lines its own title wraps to.

#card-water-monitor-lead{ height:120px }
#card-water-monitor-lead .pcard-title{ font-size:42px }
*/

/* One card given room and the rest left to divide up what is left.

#card-drone-1{ height:180px }
*/

/* And because these are real elements in the real cascade, a media query works
   the way you would expect. Bigger type on a wide window:

@media (min-width:1900px){
  #card-evtol-1 .pcard-title,
  #card-evtol-2 .pcard-title{ font-size:35px }
}
*/
