/* ============================================================
   Problem statement section
   Copy on the left, image on the right (matching the reference
   layout), with the terminal-decode text reveal on the paragraph.

   Type is Sohne, ported from D:\My Portfolio - the mini-title /
   main-title / paragraph treatment mirrors that site's "About Me"
   section. Scoped to this section only; the footer keeps its own
   Palace / Bellefair / DM Mono identity from footer.css.
   Colors still use the shared --base- tokens so the ink matches.
   ============================================================ */

@font-face {
  font-family: "sohne";
  src: url("../font/sohne--normal.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "sohne-medium";
  src: url("../font/sohne--medium.woff2") format("woff2");
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "sohne-bold";
  src: url("../font/sohne--extrabold.woff2") format("woff2");
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

.problem-statement,
.problem-statement p {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
}

/* Exactly one viewport tall - the image column stretches to fill it. */
.problem-statement {
  position: relative;
  width: 100%;
  height: 100svh;
  background-color: #ffffff;
  overflow: hidden;
}

/* 50/50 split - the image column bleeds to the right viewport edge,
   so no padding or max-width wrapper here. */
.problem-statement-inner {
  display: flex;
  align-items: center;
  width: 100%;
  height: 100%;
}

/* Copy is vertically centred in the viewport rather than padded to height. */
.problem-statement-copy {
  flex: 0 0 58%;
  max-width: 58%;
  min-width: 0;
  padding: 3rem 5rem 3rem 4rem;
}

/* Mini title - the portfolio's h5 treatment. */
.problem-statement-copy h5 {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: clamp(13px, 1.4vw, 17px);
  font-weight: 400;
  line-height: 2.3;
  text-transform: uppercase;
  color: var(--base-400);
  margin-bottom: 5px;
}

/* Main title - Bellefair, the same face as the "Epochs" marks at the top
   of the page and in the footer, set in caps to match them. Every title
   on the page is now one typeface and one case.

   Weight drops to 400 and tracking to normal: 600 was a Manrope weight
   and Bellefair has no bold cut, so it would synthesise a smeared
   faux-bold, and the old negative tracking was a lowercase-sans optical
   fix that closes a serif's caps up too far. Size is unchanged, so the
   titles keep their place in the rhythm with the Sohne mini-title. */
.problem-statement-copy h2 {
  font-family: "Bellefair", "Palace", serif;
  font-size: clamp(21px, 2.5vw, 37px);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: normal;
  text-transform: uppercase;
  margin-bottom: 1.75rem;
  /* Bellefair ships a 400 regular only (fonts/bellefair/), so there is no
     bold cut to load and `font-weight: 600` would only get the browser's
     synthetic smear. Weight is added by stroking the real outline instead,
     which thickens evenly and keeps the counters open. Paired with the
     darkest ink so the titles carry more presence at the reduced size.

     Tracking is pulled negative: stroking widens each glyph, so caps set
     at `normal` read loose once thickened - the tightening compensates. */
  color: var(--base-500);
  letter-spacing: -0.02em;
  -webkit-text-stroke: 1px currentColor;
}

/* Body - the portfolio's p treatment.
   The reveal writes color per character, so no color is set here. */
.problem-statement-copy p {
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 400;
  line-height: 1.8;
  letter-spacing: normal;
  /* Widened from 34rem, which stopped the measure well short of the
     column's inner edge and left a conspicuous gap on the right. The
     column is 58% wide with 5rem of right padding, so the padding is
     what sets the true margin - this just stops the text truncating
     before it. */
  max-width: 46rem;
}

/* The turn - the paragraph that reveals the empty rooms. Split off from
   the scenario above it so the reveal lands as its own beat rather than
   as the tail of a long block.

   A <br> cannot do this job here: animated-copy.js splits a paragraph by
   reading element.textContent, which drops markup, so an inline break
   would be deleted the moment the reveal initialises. The two beats are
   separate <p>s inside a [data-copy-wrapper] instead, which the script
   reveals as one sequence under a single ScrollTrigger. */

/* The body treatment is set on `.problem-statement-copy p`, which is a
   descendant selector and so still reaches these paragraphs now that the
   wrapper sits between them and the column - but the wrapper itself would
   otherwise impose the browser's default font on nothing and the measure
   on everything, so the cap is restated here. Without it the wrapper is
   full-column width and the 46rem measure is lost. */
.problem-statement-copy [data-copy-wrapper] {
  max-width: 46rem;
}

/* A blank line between the beats - one full line-height, so the gap reads
   as an empty line in the copy rather than as loosened paragraph spacing.
   Matches the 1.8 line-height set on the body treatment. */
.problem-statement-copy p + .problem-statement-turn {
  margin-top: 1.8em;
}

/* A cut corner - the panel is a rectangle with its top-outer corner
   sliced clean off on a diagonal, like a trimmed archival plate or a
   clipped index card. Hard edges, no softening: the shape reads as a
   deliberate cut rather than a rounded-off box.

   clip-path rather than border-radius because the cut is a straight
   chamfer, not a curve. The two cuts sit at DIAGONALLY OPPOSITE corners
   - top-right and bottom-left - so the panel reads as rotationally
   symmetrical rather than lopsided.

   The chamfer is sized in absolute px via --cut, NOT in percentages.
   Percentages on a polygon resolve against different axes - the X pair
   against width, the Y pair against height - so on a 605x648 panel a
   "28%/18%" cut gives a 169px horizontal leg against a 117px vertical
   one: a 35 degree bevel, not 45, and the angle drifts at every
   viewport as the box proportion changes. Equal px legs give a true
   45 degree cut that stays 45 degrees at any size. */
.problem-statement-media {
  --cut: clamp(2rem, 4.5vw, 4.5rem);

  flex: 0 0 42%;
  max-width: 42%;
  min-width: 0;
  height: 72vh;
  align-self: center;
  overflow: hidden;
  clip-path: polygon(
    0 0,
    calc(100% - var(--cut)) 0,
    100% var(--cut),
    100% 100%,
    var(--cut) 100%,
    0 calc(100% - var(--cut))
  );
}

.problem-statement-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ============================================================
   Top-section arc - the footer's curved scrolling text, flipped.
   The footer arc curves upward; this is its mirror, curving down,
   sitting in the empty section at the top of the page.
   ============================================================ */
.page {
  position: relative;
  display: flex;
  align-items: center;
  overflow: hidden;
}

/* Centred in the section, as it was originally. The upward arc peaks at
   mid-span, so the title block below carries the offset that keeps the
   two apart - see .page-title. */
.page-arc {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 120%;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* The title sits inside the arc's downward curve - the bow reaches its
   lowest point mid-span, so the type sits above that trough. */
.page-title {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 1100px;
  /* The arc is centred, so the title is positioned relative to the
     curve rather than by moving the arc. It nests INSIDE the trough at
     mid-span - the bow's lowest point - so the type reads as sitting in
     the arc's hollow, mirroring how the footer wordmark sits in its
     bowl. Pulled up from the old 52vh, which cleared the descending
     tail entirely and left the title stranded below the curve.
     vh-anchored because the relationship is vertical. */
  margin: clamp(14vh, 20vh, 24vh) auto 0 auto;
  padding: 0 2rem;
  text-align: center;
}

.page-title h5 {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: clamp(13px, 1.4vw, 17px);
  font-weight: 400;
  line-height: 2.3;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--base-400);
  margin-bottom: 1rem;
}

/* Bellefair, set explicitly here and identically on .footer-brand h1 -
   the two "Epochs" titles are the same mark and must read as one
   typeface. Both override the global h1 rule (Palace), so if one moves
   the other has to move with it. */
.page-title h1 {
  font-family: "Bellefair", "Palace", serif;
  font-size: clamp(2.75rem, 6.5vw, 6.5rem);
  line-height: 1.02;
  /* Caps, matching .footer-brand h1 - which sets no text-transform and so
     takes the global heading rule's uppercase. The two "Epochs" marks are
     the same mark and must read as one, so this drops the old `none`
     override rather than restating uppercase here. */
  color: var(--base-500);
}

.page-subtitle {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 400;
  line-height: 1.8;
  letter-spacing: normal;
  color: var(--base-400);
  max-width: 40rem;
  margin: 1.5rem auto 0 auto;
}

@media (max-width: 1000px) {
  .page-title {
    padding: 0 1.5rem;
  }
}

.page-arc-svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.page-arc-text {
  font-family: "Palace", serif;
  font-size: 1.25rem;
  font-weight: 600;
  letter-spacing: 5px;
  text-transform: uppercase;
  fill: var(--base-400);
}

/* The separator dot between repeats, in the animated-copy reveal's accent
   green (#abff02, see COLOR_ACCENT in animated-copy.js) - a small accent
   rather than recoloring the whole line, matching the glow behind it and
   the charts below. */
.page-arc-dot {
  fill: #7a9d1a;
  /* The four-pointed star carries a far larger visual body than the
     middot it replaced, so it is stepped down to sit as a separator
     rather than competing with the words either side of it.

     Note this glyph is NOT coming from Palace - that face has no
     U+2726 (nor the U+00B7 the dot used before it), so the browser
     substitutes a fallback whose metrics are its own. The value below
     is tuned to how that substitute actually renders. */
  font-size: 0.70em;
}

@media (max-width: 1000px) {
  .page-arc {
    width: 180%;
  }

  .page-arc-text {
    font-size: 2rem;
  }
}

/* ============================================================
   Solution - the mirror of the problem block above: image flush
   to the LEFT viewport edge, copy on the right.
   ============================================================ */
.solution-statement {
  position: relative;
  width: 100%;
  height: 100svh;
  background-color: #ffffff;
  overflow: hidden;
}

.solution-statement-inner {
  display: flex;
  align-items: center;
  width: 100%;
  height: 100%;
}

/* The mirror of .problem-statement-media's pair of cuts. This panel
   sits on the left, so the chamfers move to top-LEFT and bottom-RIGHT
   - still diagonally opposite each other, mirrored across the page.
   --cut is held identical so both panels carry the same 45 degree
   bevel at the same size. */
.solution-statement-media {
  --cut: clamp(2rem, 4.5vw, 4.5rem);

  flex: 0 0 42%;
  max-width: 42%;
  min-width: 0;
  height: 72vh;
  align-self: center;
  overflow: hidden;
  order: -1;
  clip-path: polygon(
    var(--cut) 0,
    100% 0,
    100% calc(100% - var(--cut)),
    calc(100% - var(--cut)) 100%,
    0 100%,
    0 var(--cut)
  );
}

.solution-statement-media img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.solution-statement-copy {
  flex: 0 0 58%;
  max-width: 58%;
  min-width: 0;
  padding: 3rem 4rem 3rem 5rem;
}

.solution-statement-copy h5 {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: clamp(13px, 1.4vw, 17px);
  font-weight: 400;
  line-height: 2.3;
  text-transform: uppercase;
  color: var(--base-400);
  margin-bottom: 5px;
}

/* Face, weight, case and tracking held identical to
   .problem-statement-copy h2 - the section titles are one treatment and
   must move together. */
.solution-statement-copy h2 {
  font-family: "Bellefair", "Palace", serif;
  font-size: clamp(21px, 2.5vw, 37px);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  margin-bottom: 1.75rem;
  color: var(--base-500);
  -webkit-text-stroke: 1px currentColor;
}

/* The reveal writes color per character, so no color is set here.
   Measure held identical to .problem-statement-copy p - the two blocks
   mirror each other, so the copy has to run to the same width in both
   or the pair reads lopsided. */
.solution-statement-copy p {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 400;
  line-height: 1.8;
  letter-spacing: normal;
  max-width: 46rem;
}

.solution-statement .word {
  display: inline-block;
  white-space: nowrap;
}

.solution-statement .char {
  display: inline-block;
  transition: color 150ms ease-out;
  will-change: color;
}

/* ============================================================
   Comparison + live CTA, under the solution block.
   Structure ported from freeroom-booking's .compare component,
   restyled in this page's tokens and Sohne/Manrope type.
   ============================================================ */
.comparison {
  position: relative;
  width: 100%;
  padding: 8rem 4rem 5rem 4rem;
  background-color: #ffffff;

  /* Same ramp the charts use, so the whole lower page is one system. */
  --ink-900: #171512;
  --ink-800: #2b2823;
  --ink-700: #423e37;
  --ink-600: #5c574e;
  --ink-500: #7a746a;
  --ink-400: #8b857a;
  --ink-300: #bdb8ae;
  /* 200 and 100 complete the ramp to match app.css - the mechanic block
     below reuses the live app's card and button styles, which reference
     both, and an undefined token silently resolves to transparent. */
  --ink-200: #d0cbc0;
  --ink-150: #e6e2d9;
  --ink-100: #efece5;
  --border-strong: rgba(23, 21, 18, 0.2);
  --surface: #ffffff;
}

.comparison-inner {
  width: 100%;
  /* Caps the whole section - eyebrow, heading, paragraph row and CTA.
     Raised from 1100px so the two-up paragraphs get a real measure;
     .comparison-paras cannot exceed this, so widening the child alone
     did nothing. */
  max-width: 1560px;
  margin: 0 auto;
  text-align: center;
}

.comparison h5 {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: clamp(13px, 1.4vw, 17px);
  font-weight: 400;
  line-height: 2.3;
  text-transform: uppercase;
  color: var(--base-400);
  margin-bottom: 5px;
}

/* ============================================================
   Problem aside - a centred eyebrow/headline/body block sitting
   between the problem statement and the charts below it.
   ============================================================ */
.problem-aside {
  width: 100%;
  /* Vertical padding only - the side value stays 2rem to hold the page
     gutter. Tightened from 8rem so the block sits closer to the sections
     either side of it. */
  padding: 4rem 2rem;
  background-color: #ffffff;
}

.problem-aside-inner {
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  text-align: center;
}

/* Same Sohne mini-title treatment as every other section eyebrow -
   letter-spacing held at the default (normal), matching
   .problem-statement-copy h5 exactly rather than tracking wider. */
.problem-aside h5 {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: clamp(13px, 1.4vw, 17px);
  font-weight: 400;
  line-height: 2.3;
  text-transform: uppercase;
  color: var(--base-400);
  margin-bottom: 5px;
}

/* Same Sohne body treatment as the sections above and below. */
.problem-aside p {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 400;
  line-height: 1.8;
  letter-spacing: normal;
  color: var(--base-400);
  max-width: 64rem;
  margin: 0 auto;
}

/* The wrapper [data-copy-wrapper] adds sits between .problem-aside-inner
   and these paragraphs, so the descendant rule above still reaches them for
   type - but the measure cap needs restating here, or the wrapper (a plain
   block) stretches to the inner's full 1100px and the 64rem cap is lost. */
.problem-aside [data-copy-wrapper] {
  max-width: 64rem;
  margin: 0 auto;
}

/* A blank line between the two paragraphs - one full line-height, matching
   the same "empty line" treatment used between the two beats in
   .problem-statement-copy, so both sections handle a paragraph break the
   same way. */
.problem-aside p + .problem-aside-followup {
  margin-top: 1.8em;
}

@media (max-width: 1000px) {
  .problem-aside {
    padding: 2.5rem 1.5rem;
  }

  /* Left-aligned on mobile, matching every other paragraph block's mobile
     treatment (.problem-detail p[data-animated-copy] does the same) -
     centred text reads fine as a short desktop headline but turns ragged
     and harder to scan once it wraps to several lines at phone width. */
  .problem-aside-inner {
    text-align: left;
  }

  .problem-aside p,
  .problem-aside [data-copy-wrapper] {
    margin-left: 0;
    margin-right: 0;
  }
}

/* Face, weight, case and tracking held identical to
   .problem-statement-copy h2 - the section titles are one treatment and
   must move together. */
.problem-aside h2,
.comparison h2 {
  font-family: "Bellefair", "Palace", serif;
  font-size: clamp(21px, 2.5vw, 37px);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  margin-bottom: 3.5rem;
  /* Matches .problem-statement-copy h2 / .solution-statement-copy h2 -
     the section titles are one treatment. This used to take --ink-700,
     this section's own darkest ink, which is a warm mid grey: next to
     the near-black --base-500 of the other two titles it read as washed
     out rather than as the same heading. The stroke stays (see the note
     on .problem-statement-copy h2 - it is how Bellefair gets its weight
     without a bold cut) and now strokes in the darker ink along with it. */
  color: var(--base-500);
  -webkit-text-stroke: 1px currentColor;
}

/* The shared rule above sets 3.5rem, which is the comparison block's gap
   down to its table. This block runs straight into body copy, so it needs
   the tighter gap. Colour is no longer overridden here - the shared rule
   now sets --base-500 for both. Placed AFTER the shared rule: the two
   selectors have equal specificity, so source order decides. */
.problem-aside h2 {
  margin-bottom: 1.75rem;
}

/* Two paragraphs sharing a row - one left, one right. Left-aligned
   inside the centred section: centring the copy itself would leave both
   columns on ragged inner edges and stop them reading as a pair.
   auto-fit collapses them to a single stack when the row can no longer
   hold two at the min width. */
.comparison-paras {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 4rem;
  width: 100%;
  /* Matches .comparison-inner's cap so the parent is the only limit -
     a smaller value here would silently become the real bottleneck. */
  max-width: 100%;
  margin: 0 auto;
  text-align: left;
}

/* Same Sohne body treatment as every other paragraph on the page. The
   reveal writes color per character, so no color is set here. */
.comparison-paras p {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 400;
  line-height: 1.8;
  letter-spacing: normal;
}

/* The closing line under the two-up paras - back to the section's own
   centred convention (.comparison-inner is text-align: center) rather
   than the left-aligned pair above it, so the section opens centred,
   splits into two columns to make its case, then closes centred again. */
.comparison-closer {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 400;
  line-height: 1.8;
  letter-spacing: normal;
  max-width: 46rem;
  margin: 3rem auto 0 auto;
  text-align: center;
}

.compare {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1px;
  background: var(--ink-150);
  border: 1px solid var(--ink-150);
  border-radius: 6px;
  overflow: hidden;
  text-align: left;
}

.compare__col {
  background: var(--surface);
  padding: 2rem 1.75rem;
}

/* The "ours" column is lifted with a wash rather than a colour, so it
   stays inside the monochrome system the charts established. */
.compare__col--ours {
  background: #fbfaf7;
}

.compare__head {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-500);
  margin-bottom: 1.25rem;
}

.compare__col--ours .compare__head {
  color: var(--ink-900);
}

.compare__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
}

.compare__item {
  display: flex;
  gap: 10px;
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 14px;
  font-weight: 400;
  line-height: 1.55;
  letter-spacing: normal;
  color: var(--ink-600);
}

.compare__glyph {
  flex-shrink: 0;
  color: var(--ink-300);
}

.compare__col--ours .compare__item {
  color: var(--ink-800);
}

.compare__col--ours .compare__glyph {
  color: var(--ink-900);
}

/* --- Live CTA -------------------------------------------------------------- */
/* A cut corner rather than a rounded one - the same chamfer language as
   the photos and the section's stroked headings, so the button reads as
   part of this page's system instead of a stock rounded CTA dropped in.
   Only the bottom-right corner is cut, at a small fixed size: a button
   this small can't carry the photos' full 45deg bevel without losing its
   shape, so this is a nod to that language rather than a literal match. */
.comparison-cta {
  --cta-cut: 10px;

  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 11px;
  margin-top: 3rem;
  padding: 17px 34px;
  background: var(--ink-900);
  border: none;
  clip-path: polygon(
    0 0,
    100% 0,
    100% calc(100% - var(--cta-cut)),
    calc(100% - var(--cta-cut)) 100%,
    0 100%
  );
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 14.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #ffffff;
  text-decoration: none;
  transition: background-color 260ms ease, transform 260ms ease, box-shadow 260ms ease;
  box-shadow: 0 1px 2px rgba(23, 21, 18, 0.14);
}

.comparison-cta:hover {
  background: var(--ink-800);
  transform: translateY(-2px);
  box-shadow: 0 6px 16px -4px rgba(23, 21, 18, 0.32);
}

.comparison-cta:active {
  transform: translateY(0);
  box-shadow: 0 1px 2px rgba(23, 21, 18, 0.14);
}

.comparison-cta svg {
  transition: transform 260ms ease;
}

.comparison-cta:hover svg {
  transform: translateX(4px);
}

.comparison-cta:focus-visible {
  outline: 2px solid var(--ink-900);
  outline-offset: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .comparison-cta,
  .comparison-cta:hover,
  .comparison-cta svg,
  .comparison-cta:hover svg {
    transition: none;
  }
}

/* ============================================================
   Problem detail - longer copy + the occupancy chart.
   Sits below the viewport-locked block above, so it takes its
   own natural height rather than being squeezed into one screen.
   ============================================================ */
.problem-detail {
  position: relative;
  width: 100%;
  padding: 10rem 4rem 8rem 4rem;
  background-color: #ffffff;
}

/* Copy centred on its own line, the two charts sharing the row below it. */
.problem-detail-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5rem;
  width: 100%;
  max-width: 1300px;
  margin: 0 auto;
}

/* Same Sohne body treatment as the section above - the reveal
   writes color per character, so no color is set here. */
.problem-detail p[data-animated-copy] {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 400;
  line-height: 1.8;
  max-width: 64rem;
  text-align: center;
}

/* The two figures, side by side. Each sizes its own chart from its
   own clientWidth, so they redraw to fit half the row. */
.problem-figures {
  display: flex;
  align-items: flex-start;
  gap: 4rem;
  width: 100%;
}

.problem-figures > .problem-figure {
  flex: 1 1 0;
  min-width: 0;
}

.problem-detail .word {
  display: inline-block;
  white-space: nowrap;
}

.problem-detail .char {
  display: inline-block;
  transition: color 150ms ease-out;
  will-change: color;
}

.problem-figure {
  margin: 0;
  width: 100%;
}

/* --- Chart furniture -------------------------------------------------------
   Tokens and rules ported from freeroom-booking (app.css + editorial.css)
   so the engine in charts.js draws exactly as it does on that page. The
   --ink-* ramp is what the SVG marks are colored from; without it the
   chart renders but is invisible. */
.problem-detail {
  --ink-900: #171512;
  --ink-800: #2b2823;
  --ink-700: #423e37;
  --ink-600: #5c574e;
  --ink-500: #7a746a;
  --ink-400: #8b857a;
  --ink-300: #bdb8ae;
  --ink-200: #d0cbc0;
  --ink-150: #e6e2d9;
  --ink-100: #efece5;
  --surface: #ffffff;
  --border: rgba(23, 21, 18, 0.11);
  --border-strong: rgba(23, 21, 18, 0.2);
  --shadow: 0 1px 3px rgba(23, 21, 18, 0.06),
    0 8px 20px -10px rgba(23, 21, 18, 0.13);
}

/* The engine sets no font-family - SVG <text> inherits it from here.
   Without this the marks fall back to the page's serif body font. */
.problem-detail .chart,
.problem-detail .chart text {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.problem-detail .chart {
  position: relative;
  width: 100%;
  min-height: 200px;
  display: grid;
  align-content: center;
}

.problem-detail .chart--center {
  place-items: center;
}

.problem-detail .chart-slice {
  transition: opacity 0.12s linear;
}

.problem-detail .chart-slice.is-selectable {
  cursor: pointer;
}

.problem-detail .chart-slice:hover,
.problem-detail .chart-slice:focus-visible {
  opacity: 0.75;
  outline: none;
}

.problem-detail .chart-slice.is-dimmed {
  opacity: 0.22;
}

.problem-detail .chart svg {
  display: block;
  overflow: visible;
}

.problem-detail .chart-empty {
  padding: 44px 12px;
  text-align: center;
  color: var(--ink-400);
  font-size: 12.5px;
}

.problem-detail .chart-column,
.problem-detail .chart-cell {
  cursor: default;
  transition: opacity 0.12s linear;
}

.problem-detail .chart-column:hover,
.problem-detail .chart-cell:hover {
  opacity: 0.78;
}

.problem-detail .chart-column:focus,
.problem-detail .chart-column:focus-visible,
.problem-detail .chart-cell:focus,
.problem-detail .chart-cell:focus-visible {
  outline: none;
}

/* footer.css sets a global `p { font-family: Bellefair }`; this has to
   out-specify it or the caption renders serif against a sans chart. */
.problem-detail .problem-figure .figure__caption,
.problem-detail .figure__caption {
  margin-top: 13px;
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 12px;
  line-height: 1.5;
  letter-spacing: normal;
  color: var(--ink-400);
  /* Centred under its own chart, so each caption reads as belonging to the
     mark above it rather than to the row. `margin-inline: auto` is what
     actually does the centring - `text-align` alone would centre the lines
     inside a 60ch box still parked on the figure's left edge. */
  max-width: 60ch;
  margin-inline: auto;
  text-align: center;
}

.problem-detail .figure__caption strong {
  color: var(--ink-700);
  font-weight: 600;
}

/* Figure title - sits above its chart, centred over the mark it names.
   Sohne like every other label on this page, and only a step up from the
   caption: it identifies the chart, it is not a section heading. */
.problem-detail .figure__title {
  margin: 0 0 14px 0;
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: normal;
  color: var(--ink-700);
  text-align: center;
}

/* Read more - native <details>, so it opens with a click or a keypress and
   works with no JavaScript at all. */
.problem-detail .figure__more {
  margin-top: 10px;
}

.problem-detail .figure__more > summary {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  width: fit-content;
  margin-inline: auto;
  font-weight: 600;
  color: var(--ink-700);
  cursor: pointer;
  /* The caption is centred, so the marker is drawn rather than inherited -
     a list-item marker would hang off the centred line instead of sitting
     against the label. */
  list-style: none;
}

.problem-detail .figure__more > summary::-webkit-details-marker {
  display: none;
}

.problem-detail .figure__more > summary::after {
  content: "";
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-2px) rotate(45deg);
  transition: transform 180ms ease;
}

.problem-detail .figure__more[open] > summary::after {
  transform: translateY(1px) rotate(-135deg);
}

.problem-detail .figure__more > summary:focus-visible {
  outline: 2px solid var(--ink-700);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Inherits the caption's size, colour and centring - the expanded text is
   the same voice as the line that introduced it, just longer. */
.problem-detail .figure__more p {
  margin: 10px 0 0 0;
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 12px;
  line-height: 1.5;
  letter-spacing: normal;
  color: var(--ink-400);
  text-align: center;
}

@media (prefers-reduced-motion: reduce) {
  .problem-detail .figure__more > summary::after {
    transition: none;
  }
}

/* --- Chart tooltip -------------------------------------------------------- */
.problem-detail .chart-tooltip {
  position: absolute;
  left: 0;
  top: 0;
  width: max-content;
  pointer-events: none;
  will-change: transform;
  z-index: 5;
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.problem-detail .chart-tooltip__box {
  position: relative;
  min-width: 172px;
  max-width: 260px;
  padding: 9px 11px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 4.5px;
  box-shadow: var(--shadow);
  transform-origin: var(--ox, 0%) var(--oy, 100%);
  opacity: 0;
  transform: scale(1.012, 0.93) translateY(5px);
  transition: opacity 0.16s ease, transform 0.16s ease;
}

.problem-detail .chart-tooltip.is-visible .chart-tooltip__box {
  opacity: 1;
  transform: scale(1, 1) translateY(0);
  transition: opacity 0.3s cubic-bezier(0.3, 0.6, 0.3, 1),
    transform 0.42s cubic-bezier(0.18, 0.9, 0.26, 1.02);
}

.problem-detail .chart-tooltip.is-adopting .chart-tooltip__box,
.problem-detail .chart-tooltip.is-adopting .chart-tooltip__layer > * {
  transition: none !important;
  animation: none !important;
}

.problem-detail .chart-tooltip__layer--ghost {
  position: absolute;
  left: 11px;
  right: 11px;
  top: 9px;
  opacity: 0;
  pointer-events: none;
}

.problem-detail .chart-tooltip__title {
  font-size: 12px;
  font-weight: 600;
  margin-bottom: 6px;
}

.problem-detail .chart-tooltip__row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  margin-top: 3px;
}

.problem-detail .chart-tooltip__name {
  color: var(--ink-500);
}

.problem-detail .chart-tooltip__value {
  margin-left: auto;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.problem-detail .chart-tooltip__meta {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--border);
  font-size: 11.5px;
  color: var(--ink-400);
}

/* Split-text primitives - mirrors GSAP SplitText's output classes. */
.problem-statement .word {
  display: inline-block;
  white-space: nowrap;
}

.problem-statement .char {
  display: inline-block;
  transition: color 150ms ease-out;
  will-change: color;
}

@media (max-width: 1000px) {
  .problem-detail {
    padding: 5rem 1.5rem 5rem 1.5rem;
  }

  /* Stacked columns need natural height, same as the hero block. */
  .solution-statement {
    height: auto;
  }

  .solution-statement-inner {
    flex-direction: column;
    align-items: stretch;
    height: auto;
  }

  .solution-statement-copy,
  .solution-statement-media {
    flex: 0 0 auto;
    max-width: 100%;
  }

  /* order:-1 puts the image left on desktop; stacked it would drag it
     above nothing, so reset it and let DOM order stand. The desktop
     72vh panel height must release here or it fights aspect-ratio. */
  .solution-statement-media {
    order: 0;
    height: auto;
    min-height: 0;
    aspect-ratio: 3 / 2;
  }

  .solution-statement-copy {
    padding: 3rem 1.5rem 5rem 1.5rem;
  }

  .comparison {
    padding: 5rem 1.5rem 6rem 1.5rem;
  }

  .comparison h2 {
    margin-bottom: 2.5rem;
  }

  /* .comparison-inner becomes a flex column with gap: 2.5rem below, and
     the eyebrow and heading are items of it (see the note there). The
     gap then ADDS to the h5's own margin-bottom: 5px, so the eyebrow
     sits ~2.5rem clear of the title it belongs to - a gap none of the
     other three eyebrows have, since their parents stay plain blocks at
     every width and collapse margins normally.

     Zeroing the margin and pulling the pair back together with a
     negative top margin on the h2 cancels the inherited gap, so the
     eyebrow reads as attached to its heading here the way it does in
     .problem-statement-copy, .problem-aside-inner and
     .solution-statement-copy. */
  .comparison h5 {
    margin-bottom: 0;
  }

  .comparison h5 + h2 {
    margin-top: calc(-2.5rem + 5px);
  }

  /* --- Mobile reading order: para, loop, para, loop ------------------------
     On desktop the two loops sit side by side above both paragraphs. At
     phone width that means scrolling past both animations before reading a
     word about either, so each paragraph is paired with the loop it
     describes: para 01 (spotting a free room and dragging it open) sits
     above step 01, para 02 (the student votes that keep it live) above
     step 02.

     Done with order rather than by moving the markup, because neither the
     <p>s nor the figures can be reparented: animated-copy.js splits the
     CHILDREN of [data-copy-wrapper], so moving the <p>s out of
     .comparison-paras breaks the reveal (see the note in index.html).

     WHY .comparison-inner becomes the flex container.
     .comparison-paras and .mechanic are SIBLINGS, both children of
     .comparison-inner - not parent and child. So dissolving
     .comparison-paras alone promoted its two <p>s into .comparison-inner,
     which is a plain block: `order` is ignored outside a flex/grid
     container, so the computed orders below were all correct and the page
     still rendered loop, loop, para, para. Interleaving requires the two
     <p>s and the two figures to be items of ONE container, which means
     both wrappers dissolve and their shared parent does the layout.
     -------------------------------------------------------------------- */

  /* The real container at this width. The heading block (h5, h2) are items
     too and keep their natural place at the top via order: 0; the closer
     and CTA follow the ordered pairs for the same reason. */
  .comparison-inner {
    display: flex;
    flex-direction: column;
    gap: 2.5rem;
  }

  /* Both wrappers dissolve so all four items land in the flex column
     above. animated-copy.js reads the wrapper's children once at init and
     splits them in place, so dissolving .comparison-paras is invisible to
     the reveal - the element itself stays in the DOM and the <p>s stay its
     children.

     .mechanic dissolving is what the note here used to rule out, because
     it was mechanic-loop.js's ScrollTrigger trigger and display: contents
     generates no box, collapsing start/end onto a point. That script now
     triggers on .comparison instead, which always has a box at every
     width, so it is safe - see the comment in mechanic-loop.js.

     Only .comparison-paras is declared here. .mechanic's own
     `display: contents` cannot live in this block: the unconditional
     `.mechanic` rule that sets `display: grid` sits AFTER it in the file,
     and at equal specificity source order wins, so an override here is
     silently discarded. It is declared in this section's LAST media block
     instead - search "display: contents". */
  .comparison-paras {
    display: contents;
  }

  /* The pairs, in reading order. */
  .comparison-paras p:first-child { order: 1; }
  .mechanic__step:first-child { order: 2; }
  .comparison-paras p:last-child { order: 3; }
  .mechanic__step:last-child { order: 4; }

  /* Both trail the interleaved pairs rather than floating into them. */
  .comparison-closer { order: 5; }
  .comparison-cta { order: 6; }

  /* The flex gap owns the vertical rhythm now, so the margins these carry
     as prose (and .mechanic's own 4rem bottom margin, which is gone with
     its box) would double it up. */
  .comparison-paras p {
    margin: 0;
    text-align: left;
  }

  .mechanic__step {
    text-align: left;
  }

  .comparison-closer {
    margin-top: 0;
  }

  /* Was margin-top: 3rem on a block in normal flow; as a flex item with
     align-self it no longer stretches full width, so the auto margins
     that used to centre it are replaced by the flex alignment. */
  .comparison-cta {
    margin-top: 0;
    align-self: center;
  }

  .compare__col {
    padding: 1.75rem 1.5rem;
  }

  .problem-detail-inner {
    align-items: stretch;
    gap: 3rem;
  }

  .problem-detail p[data-animated-copy] {
    text-align: left;
  }

  /* Two charts side by side would be unreadable at phone width. */
  .problem-figures {
    flex-direction: column;
    gap: 3.5rem;
  }

  /* Stacked columns need natural height - one viewport would crush both. */
  .problem-statement {
    height: auto;
  }

  .problem-statement-inner {
    flex-direction: column;
    align-items: stretch;
    height: auto;
  }

  /* Stacked: copy carries its own padding, image still spans full width. */
  .problem-statement-copy,
  .problem-statement-media {
    flex: 0 0 auto;
    max-width: 100%;
  }

  .problem-statement-copy {
    padding: 5rem 1.5rem 3rem 1.5rem;
  }

  .problem-statement-media {
    height: auto;
    min-height: 0;
    aspect-ratio: 3 / 2;
  }
}

/* ============================================================
   Mechanic pair - "how claiming actually works", under the two
   photographs in .comparison.

   These are rebuilt in markup rather than screenshotted. The photos
   above are atmospheric, 460px tall and chamfered; a UI capture has
   the opposite properties - dense, rectangular, full of small type -
   so dropping one into the same frame fights the chamfer, blurs on
   retina and forces the interface text below a readable size. Built
   natively it stays sharp, inherits this section's ink ramp, and
   reflows on small screens instead of scaling down.
   ============================================================ */
.mechanic {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 4rem;
  width: 100%;
  margin: 0 auto 4rem auto;
  text-align: left;
}

.mechanic__step {
  margin: 0;
}

/* The device: a plain bordered card, deliberately quieter than the
   photographs so it reads as evidence rather than a third picture. */
.mechanic__frame {
  padding: 18px;
  background: var(--surface);
  border: 1px solid var(--ink-150);
  border-radius: 6px;
  box-shadow: 0 1px 3px rgba(23, 21, 18, 0.05),
              0 18px 40px -28px rgba(23, 21, 18, 0.3);
}

.mechanic__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-bottom: 12px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--ink-150);
}

.mechanic__label,
.mechanic__stamp,
.mechanic__pill,
.mechanic__hint,
.mechanic__reporthead,
.mechanic__conf,
.mechanic__vote,
.mechanic__slot,
.mechanic__hours {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
}

.mechanic__label {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-900);
}

.mechanic__stamp {
  font-size: 11px;
  color: var(--ink-400);
}

.mechanic__pill {
  padding: 3px 9px;
  border: 1px solid var(--ink-900);
  border-radius: 999px;
  background: var(--ink-900);
  color: var(--surface);
  font-size: 10.5px;
  font-weight: 600;
  white-space: nowrap;
}

/* --- Step 01: the drag ---------------------------------------------------- */

.mechanic__grid {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 10px;
}

.mechanic__hours {
  display: grid;
  grid-template-rows: repeat(4, 1fr);
  font-size: 10.5px;
  color: var(--ink-400);
  text-align: right;
}

.mechanic__hours span {
  /* Nudged up so each number sits on its rule, not between two. */
  transform: translateY(-5px);
}

.mechanic__track {
  position: relative;
  display: grid;
  grid-template-rows: repeat(4, 46px);
  border-top: 1px solid var(--ink-150);
}

.mechanic__rule {
  border-bottom: 1px solid var(--ink-150);
}

.mechanic__slot {
  position: absolute;
  left: 4px;
  right: 4px;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  /* Matches .timegrid__event / .timegrid__band in the live app. The
     border radius and fill are set per-state below. */
  padding: 4px 7px;
  font-size: 11.5px;
  font-weight: 500;
  overflow: hidden;
}

/* A class already on the timetable: flat, muted, not the subject. */
/* Geometry only - see the drag block below for its fill and border. */
.mechanic__slot--set {
  top: 4px;
  height: 38px;
}

/* The selection being dragged open. Geometry only - its fill, dashed
   border and released state are defined once in the "Drag selection,
   matched to the live app" block further down, copied from app.css. */
.mechanic__slot--drag {
  top: 60px;
  height: 76px;
}

/* Geometry only - the grip's colour is set with the drag styles below,
   where it has to read against the band's light fill. */
.mechanic__handle {
  position: absolute;
  bottom: -4px;
  left: 50%;
  width: 34px;
  height: 4px;
  margin-left: -17px;
  border-radius: 999px;
}

.mechanic__hint {
  display: flex;
  align-items: center;
  margin: 14px 0 0 0;
  font-size: 11px;
  color: var(--ink-400);
}

/* The same keycap treatment the live app uses under its grid. */
.mechanic__key {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  padding: 1px 5px;
  margin-right: 6px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-bottom-width: 2px;
  border-radius: 3px;
  font-size: 10px;
  color: var(--ink-600);
}

/* --- Step 02: the two votes ----------------------------------------------- */

.mechanic__reporthead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 10px;
  font-size: 11.5px;
  color: var(--ink-500);
}

.mechanic__meter {
  height: 5px;
  border-radius: 999px;
  background: var(--ink-150);
  overflow: hidden;
}

.mechanic__fill {
  width: 86%;
  height: 100%;
  border-radius: 999px;
  background: var(--ink-900);
}

.mechanic__conf {
  margin: 9px 0 14px 0;
  font-size: 11.5px;
  color: var(--ink-500);
}

.mechanic__votes {
  display: flex;
  gap: 8px;
}

/* Mirrors .report__votes .ghost-button from the live app, including the
   pressed state - the point of the pair is that one is already chosen. */
.mechanic__vote {
  flex: 1;
  justify-content: center;
  padding: 9px 10px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 4.5px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--ink-800);
  text-align: center;
  cursor: default;
}

.mechanic__vote[aria-pressed='true'] {
  background: var(--ink-900);
  border-color: var(--ink-900);
  color: var(--surface);
}

/* Numbered caption, same voice as .comparison-photo figcaption but with
   the step index carrying the ordering the photos don't need. */
.mechanic__step figcaption {
  display: flex;
  gap: 9px;
  margin-top: 14px;
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.55;
  color: var(--ink-500);
}

.mechanic__step figcaption span {
  flex: none;
  color: var(--ink-900);
  font-variant-numeric: tabular-nums;
}

/* The mobile .mechanic gap used to live here. It now sits with the rest of
   the mobile reading-order rules further up, so the single-column layout,
   its order values and its spacing are all in one place. */

/* --- Loop animation states ------------------------------------------------
   The two frames replay their mechanic on a loop while they are on screen.
   JS drives height/opacity/position; everything here is either the resting
   state it animates from, or the reduced-motion fallback where no JS runs
   and the markup must already read as a finished, sensible still.
   -------------------------------------------------------------------------- */

/* The votes row is the cursor's positioning context in step 02. */
.mechanic__votes {
  position: relative;
}

.mechanic__cursor {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 3;
  display: block;
  pointer-events: none;
  opacity: 0;
  /* Drawn from its tip, so the arrow point is the hotspot. */
  transform-origin: 3px 3px;
  filter: drop-shadow(0 2px 3px rgba(23, 21, 18, 0.28));
}

/* Only once JS has taken over does the slot start collapsed - without
   this guard a no-JS reader would get an invisible, zero-height slot. */
.mechanic--live .mechanic__slot--drag {
  height: 0;
  opacity: 0;
}

.mechanic--live .mechanic__slotlabel {
  opacity: 0;
}

/* The settled event at the end of the drag: solid, no longer a dashed
   in-progress selection. */
.mechanic__slot--drag.is-set {
  border-style: solid;
}

.mechanic__vote {
  position: relative;
  transition: background 0.18s linear, border-color 0.18s linear,
              color 0.18s linear;
}

/* A press, held for a beat by the timeline so the tap is legible. */
.mechanic__vote.is-press {
  transform: translateY(1px);
}

@media (prefers-reduced-motion: reduce) {
  /* No loop, no cursor - the static markup is the whole story. */
  .mechanic--live .mechanic__slot--drag,
  .mechanic--live .mechanic__slotlabel {
    height: auto;
    opacity: 1;
  }

  .mechanic--live .mechanic__slot--drag {
    height: 76px;
  }

  .mechanic__cursor {
    display: none;
  }
}

/* --- Pair alignment -------------------------------------------------------
   The two frames hold different content, so left to themselves the shorter
   one leaves its caption floating halfway up the column. Stretching both to
   the row height and pinning the captions to the bottom puts 01 and 02 on
   one baseline, which is what makes the pair read as two steps rather than
   two unrelated cards.
   -------------------------------------------------------------------------- */
.mechanic__step {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.mechanic__frame {
  flex: 1;
  display: flex;
  flex-direction: column;
}

/* Step 02's content is short; centring it in the taller frame beats
   leaving a block of dead space under the buttons. */
.mechanic__report {
  display: flex;
  flex-direction: column;
  justify-content: center;
  flex: 1;
}

/* The created event's label - a title and its time range, so the block
   carries the same information the real card does instead of reading as
   an empty slab. */
.mechanic__slotlabel {
  display: flex;
  flex-direction: column;
  gap: 3px;
  line-height: 1.35;
}

.mechanic__slotlabel strong {
  font-size: 11.5px;
  font-weight: 600;
}

.mechanic__slotlabel em {
  font-size: 10.5px;
  font-style: normal;
  opacity: 0.72;
  font-variant-numeric: tabular-nums;
}

/* Step 02 sits toward the top of its frame rather than floating in the
   middle of it - centring left a conspicuous band of empty white above
   "Student reports" once the frames were matched to a shared height. */
.mechanic__report {
  justify-content: flex-start;
}

/* The report block gets a little breathing room under the header bar
   instead of the centring that used to provide it. */
.mechanic__reporthead {
  margin-top: 2px;
}

/* --- Drag selection, matched to the live app ------------------------------
   Overrides the solid-ink first pass above. In the real grid a drag in
   progress is .timegrid__band - a 1.5px dashed --ink-700 outline over a
   barely-there rgba(23,21,18,0.06) tint, deliberately faint so the hour
   rules stay readable straight through it. It is a sketch, not a card.
   On release it becomes .timegrid__event: a solid --ink-100 fill with a
   2.5px ink rule down the left edge. Reproducing both states is the whole
   point of the loop, so they are copied from app.css rather than guessed.
   -------------------------------------------------------------------------- */
.mechanic__slot--drag {
  padding: 4px 7px;
  background: rgba(23, 21, 18, 0.06);
  border: 1.5px dashed var(--ink-700);
  border-radius: 3px;
  color: var(--ink-900);
}

.mechanic__slotlabel strong {
  font-weight: 550;
  color: var(--ink-900);
}

.mechanic__slotlabel em {
  opacity: 1;
  color: var(--ink-600);
}

/* Released: the provisional band commits to a real card. */
.mechanic__slot--drag.is-set {
  background: var(--ink-100);
  border: 1px solid rgba(23, 21, 18, 0.11);
  border-left: 2.5px solid var(--ink-700);
  border-radius: 3px;
  color: var(--ink-800);
  transition: background 0.2s linear, border-color 0.2s linear;
}

/* The resize grip reads against a light band, so it takes an ink colour
   rather than sitting invisible on its own fill.

   The separating ring is a grey, not --surface. It used to be pure white,
   and on a 4px-tall bar a 2px ring is twice the height of the thing it
   surrounds - so the grip read as a white pill overall and the dark core
   was lost inside it. A grey ring still separates the grip from the band
   edge it straddles without out-weighing the bar. */
.mechanic__handle {
  background: var(--ink-600);
  box-shadow: 0 0 0 1.5px var(--ink-200);
}

/* The existing class above it is the quieter of the two - a plain filled
   card with no left rule, so the drag stays the subject. */
.mechanic__slot--set {
  background: var(--surface);
  border: 1px solid var(--ink-150);
  color: var(--ink-500);
}

/* --- Hero sizing ----------------------------------------------------------
   With the two photographs gone, this pair is the only image in the
   section, so it carries the weight they used to. The rows grow and the
   frames get real padding - at the old size the block read as a footnote
   under the heading rather than as the section's subject.
   -------------------------------------------------------------------------- */
.mechanic {
  gap: 3rem;
}

.mechanic__frame {
  padding: 22px 24px;
}

.mechanic__track {
  /* Was 46px. Taller rows give the drag a longer, more legible gesture
     and stop the calendar frame ending in dead space. */
  grid-template-rows: repeat(4, 62px);
}

/* The timetable facts above the votes - a two-column definition list, so
   step 02 fills its frame with the information the card actually carries
   instead of leaving a band of white under the buttons. */
.mechanic__facts {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px 20px;
  margin: 0 0 20px 0;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--ink-150);
}

.mechanic__facts div {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.mechanic__facts dt {
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-400);
}

.mechanic__facts dd {
  margin: 0;
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 13px;
  font-weight: 550;
  color: var(--ink-900);
}

/* The votes are the point of this frame, so they sit at its foot with the
   facts above them rather than floating in the middle. */
.mechanic__report {
  margin-top: auto;
}

@media (max-width: 1000px) {
  .mechanic__track {
    grid-template-rows: repeat(4, 52px);
  }

  .mechanic__frame {
    padding: 18px;
  }

  /* display: contents - the other half of the mobile reading-order block
     further up this file (search "Mobile reading order"). It has to be
     declared HERE rather than there: the unconditional `.mechanic` rule
     that sets `display: grid` sits after that block, and at equal
     specificity source order wins, so the override was being discarded
     and the page still rendered both loops before both paragraphs.

     Dissolving .mechanic promotes its two figures into .comparison-inner
     (a flex column at this width) as siblings of the two paragraphs, which
     is what lets `order` interleave them into para, loop, para, loop.
     mechanic-loop.js triggers on .comparison, not .mechanic, precisely
     because this element has no box here. */
  .mechanic {
    display: contents;
  }
}

/* --- Vote buttons in the page's own button language -----------------------
   Re-cut to match .comparison-cta at the foot of this section: the same
   clipped bottom-right corner, uppercase Sohne 600, and the same lift on
   hover. The live app's plain rounded ghost-button is right inside the
   app's own chrome, but here the pair sits a few hundred pixels above the
   real CTA, and two different button idioms in one section read as an
   oversight. Scaled down from the CTA's 17px/34px - these are a control
   inside a card, not the section's closing action.
   -------------------------------------------------------------------------- */
.mechanic__vote {
  --cta-cut: 8px;

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 13px 18px;
  background: var(--surface);
  border: 1px solid var(--ink-900);
  border-radius: 0;
  clip-path: polygon(
    0 0,
    100% 0,
    100% calc(100% - var(--cta-cut)),
    calc(100% - var(--cta-cut)) 100%,
    0 100%
  );
  font-family: "sohne", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--ink-900);
  transition: background-color 260ms ease, color 260ms ease,
              transform 260ms ease, box-shadow 260ms ease;
  box-shadow: 0 1px 2px rgba(23, 21, 18, 0.14);
}

/* The chosen side fills in, the way the CTA is filled by default. */
.mechanic__vote[aria-pressed='true'] {
  background: var(--ink-900);
  border-color: var(--ink-900);
  color: #ffffff;
}

/* The loop's synthetic press, and a real one for anyone who clicks. */
.mechanic__vote.is-press,
.mechanic__vote:active {
  transform: translateY(0);
  box-shadow: 0 1px 2px rgba(23, 21, 18, 0.14);
}

/* --- The created event takes its building's colour ------------------------
   Once the drag is released the block stops being a neutral sketch and
   becomes a real card, so it picks up the building hue the live app gives
   it: the --room-ink / --room-wash pair from app.css, carried on the left
   rule with only a breath of it in the fill. B-118 uses the teal the app
   assigns that building, not an invented colour.

   The hue is never the only channel - the room name and time are printed
   on the card and the released state is already distinguished from the
   drag by a solid border and a fill, exactly as in the app. Anyone who
   cannot separate the hues loses nothing they could not already read.
   -------------------------------------------------------------------------- */
.mechanic__slot--drag.is-set {
  --room-ink: #2f6f7d;
  --room-wash: #f0f6f7;

  background: var(--room-wash);
  border: 1px solid var(--border);
  border-left: 2.5px solid var(--room-ink);
  color: var(--ink-800);
  /* Fill and rule ease in together, so the colour arrives with the
     release rather than snapping a beat after it. */
  transition: background-color 320ms ease, border-left-color 320ms ease;
}

.mechanic__slot--drag.is-set .mechanic__slotlabel strong {
  color: var(--room-ink);
}

/* --- Captions, centred and unnumbered -------------------------------------
   The numbers are gone: with only two frames side by side the reading
   order is already obvious from the layout, so "01"/"02" were labelling
   something nobody could get wrong. Centring them under each frame also
   puts them back in the section's own convention - .comparison-inner is
   centred throughout, and the left-aligned pair was the odd one out.
   -------------------------------------------------------------------------- */
.mechanic__step figcaption {
  display: block;
  margin-top: 16px;
  text-align: center;
  /* Keeps a two-line caption from running the full width of a wide frame,
     which would leave the two captions reading as one long line. */
  max-width: 34rem;
  margin-left: auto;
  margin-right: auto;
}

/* --- The created event is green -------------------------------------------
   Overrides the teal above. Green is the page's own accent - the same
   #abff02 family as the copy reveal, the arc dots and the page-transition
   wipe - so a created "free room" block reads in the site's colour rather
   than importing one of the app's building hues into a page that uses
   none of the others.

   The wash and rule are darkened well past the raw accent: #abff02 on
   white is far too light to carry a 2.5px rule or to sit under dark text,
   so the rule uses a deepened version of the same hue and the fill a very
   pale tint of it. Colour is still not the only channel - the released
   state already differs from the drag by a solid border and a fill.
   -------------------------------------------------------------------------- */
.mechanic__slot--drag.is-set {
  --room-ink: #4e7a12;
  --room-wash: #f4fbe4;
}

/* --- Notched outline that survives the clip -------------------------------
   A border cannot follow a clip-path: the browser draws the rectangular
   border first and the clip then slices the corner off, so the unpressed
   button lost its outline exactly along the diagonal and read as a plain
   square with a bite taken out of it.

   Drawn as two stacked layers instead. The element keeps the clip and
   paints the ink colour; an inset ::before, clipped to the same polygon
   one pixel in on every side, paints the surface back over it. What is
   left showing between the two is a 1px outline that follows the notch
   all the way round - the same trick the filled state gets for free by
   having no inner layer at all.
   -------------------------------------------------------------------------- */
.mechanic__vote {
  border: none;
  background: var(--ink-900);
  /* The label has to clear the ::before, which is painted over the fill. */
  isolation: isolate;
}

.mechanic__vote::before {
  content: "";
  position: absolute;
  /* 1px in on each side leaves exactly the outline weight showing. */
  inset: 1px;
  background: var(--surface);
  clip-path: polygon(
    0 0,
    100% 0,
    100% calc(100% - var(--cta-cut)),
    calc(100% - var(--cta-cut)) 100%,
    0 100%
  );
  transition: opacity 260ms ease;
  z-index: -1;
}

/* Pressed: drop the inner surface and the whole shape fills with ink,
   notch included. */
.mechanic__vote[aria-pressed='true'] {
  color: #ffffff;
}

.mechanic__vote[aria-pressed='true']::before {
  opacity: 0;
}
