/* ==========================================================================
   home.css — the Higgins London site stylesheet
   ==========================================================================
   Everything visual for the glass panel that floats over the live gradient
   background: the panel itself, its light/dark colour modes, the logo, the
   three expandable topics (Consultancy / Investments / About), the footer,
   and the background switcher in the bottom-right corner.

   Font faces are NOT registered here — they live in fonts.css, which every
   page loads before this file.

   Loaded by (always AFTER fonts.css):
   - home.html       the glass-panel home page over the live WebGL gradient
   - about.html      the About page — same shell, the third topic active
   - builder-3.html / builder-4.html
                     the in-context gradient builders — they reuse this
                     whole home shell, then load their builder CSS on top
                     for the tool chrome
   - the upload/ deployment bundle ships the same shell as index.html and
     builder.html on the live server.

   Cache stamp: every page links this file with a ?v= query, e.g.
   assets/home.css?v=20260828a. After editing this file, bump that stamp on
   EVERY page that links it, otherwise returning visitors may keep the old
   cached version.

   Companion scripts (referenced throughout the comments below):
   - home.js    sets data-state on the panel from the URL hash, toggles the
                motion classes (.intro / .intro-grow / .leaving / .entering
                / .entered), and wires the background switcher
   - content.js applies the admin portal's published content/design.json —
                the --type-scale value (the A-/A+ control) and the panel's
                data-mode
   - the builder's GUI flips data-mode live from its "panel mode" control

   This is deliberately a standalone stylesheet: the page does NOT load the
   studies' site.css (its 11px uppercase chrome defaults fight this design).

   UNITS — read this before touching any number:
   - --u is one DESIGN UNIT: exactly 1px at full scale, shrinking in
     proportion when the window is smaller than the design envelope (the
     scale rig inside .panel, below). Anything written calc(N * var(--u))
     therefore reads as "N design px". Plain px values (hairlines, the
     background switcher, the phone layout) deliberately do NOT scale.
   - --ut is the TYPE unit: --u with a floor, so the smallest step of the
     ramp never drops under 13px on any screen. Every font-size uses it
     (--uh, the heading unit, is the same thing except on phones). The
     panel width has a matching 660px floor so that type always has room.
   - --type-scale is the admin portal's A-/A+ text-size control (published
     via design.json; 1 = 100%). It multiplies TYPE ONLY, never layout —
     every font-size in the ramp carries "* var(--type-scale)".

   EDIT THIS FILE WHEN…
   - you want different panel colours in light or dark mode → the --ink /
     --hairline / --glass variables at the top of the "Glass panel" section
     (the primary EDIT HERE spot)
   - you want a bigger or smaller logo → the .logo rule (the other primary
     EDIT HERE spot)
   - you want different type sizes → the type ramp, top to bottom:
     .topic-title 48 / .portfolio-title 36 / --type-body 18 (tagline,
     summaries, collapsed titles) / .topic-copy and
     .portfolio-list 15.6 / .panel-foot and .foot-legal 14 — all TYPE
     units (--ut, floored at 13px for the 14-unit steps) multiplied by
     --type-scale
   - you want to change how topics expand/collapse → the "State machine"
     section (pure CSS — there is no show/hide JavaScript to edit)
   - phone / iPad / short-window tweaks → "Responsive layouts" at the end
   - you want to move or restyle the background switcher → its own section
   ========================================================================== */

/* ---- Base reset and page shell ----------------------------------------
   A minimal reset, then the page itself. The body colour (#fffcf3 warm
   off-white) only shows for an instant before the gradient engine paints
   its first frame — it is the "loading" colour, not the site background.
   overflow: hidden because the desktop page never scrolls; the scale rig
   (see .panel) shrinks the whole composition to fit the viewport instead.
   The phone block and the very-short-window safety valve at the end of
   this file deliberately switch overflow back to auto. */

/* Zero every default margin/padding, and make padding count INSIDE a
   declared width (border-box) — all the calc() sizing below assumes it. */
* { margin: 0; padding: 0; box-sizing: border-box; }

/* Full-height chain so percentage heights can reach the viewport. */
html, body { height: 100%; }

body {
  /* General Sans is the panel face (registered in fonts.css); everything
     after it is fallback for the instant before the font file arrives. */
  font-family: 'General Sans', ui-sans-serif, -apple-system, 'Helvetica Neue', Helvetica, Arial, sans-serif;
  background: #fffcf3;                 /* pre-gradient loading colour only */
  color: #202020;                      /* default ink; .panel overrides it via --ink */
  overflow: hidden;                    /* desktop never scrolls — see banner above */
  -webkit-font-smoothing: antialiased; /* lighter glyph rendering on macOS/iOS */
}

/* ---- Background canvases ----------------------------------------------
   The live gradient is drawn onto full-viewport <canvas class="bg">
   elements pinned behind everything else. There are two canvases in the
   HTML — one per engine (gradient / nodes) — and home.js shows whichever
   one the current background needs (the other waits underneath with
   visibility:hidden set inline). Stacking order across the whole page:
   canvases at the default z-index (0) → .panel at z-index 2 →
   .bg-switcher at z-index 3. touch-action: none lets the engines receive
   drag gestures on touch screens without the page trying to scroll. */

canvas.bg {
  position: fixed;                     /* pinned to the viewport — never scrolls away */
  inset: 0;                            /* stretch to all four edges */
  width: 100%;
  height: 100%;
  display: block;                      /* kills the inline-image baseline gap */
  touch-action: none;                  /* drags go to the engine, not page scrolling */
}

/* ---- Glass panel ------------------------------------------------------
   The frosted-glass content block. Fixed 72 design units from the
   top-left corner; height is content-driven — the panel grows and shrinks
   with its text, and home.js animates that height during state changes.
   Every text element sits exactly 24 units from the panel's left edge
   (--inset, applied as container padding); the hairline rules are
   container borders so they span the full panel width. The blur(65px)
   backdrop-filter is what makes it read as glass over the moving
   gradient. */

/* EDIT HERE: light/dark mode colour variables.
   Everything mode-dependent in the panel rides on these three variables —
   change them here and the whole panel follows:
   - --ink      text and icon colour (the arrows pick it up via
                currentColor, so they recolour automatically too)
   - --hairline the thin divider lines (borders between sections) — the
                last number is opacity, 0–1: raise 0.28 for stronger lines
   - --glass    the panel's translucent background tint — raise the 0.2
                opacity for a milkier, more solid panel; lower it to let
                more gradient through
   The first block is LIGHT mode (the default); the [data-mode="dark"]
   block below it is DARK mode. Mode is switched by the data-mode
   attribute on <main class="panel"> — hard-coded to "light" in the page
   markup, overwritten from the published design.json by content.js, and
   flipped live by the "panel mode" control in the builder's GUI. The
   0.35s cross-fade when it flips lives in the Motion section. */
.panel {
  --ink: #202020;
  --hairline: rgba(32, 32, 32, 0.28);
  --glass: rgba(255, 255, 255, 0.2);
  /* EDIT HERE: the proportional scale rig. Every size in the panel is a
     multiple of --u, one scale unit derived from the viewport. At full size
     --u is 1px, so numbers read as ordinary pixels; when the window gets
     shorter or narrower than the design envelope, --u shrinks and the WHOLE
     composition (type, insets, arrows) scales down in perfect proportion —
     so even the tallest state (CMO & Boards, --design-h) always fits with
     nothing bleeding or clipped.
     - --design-h: tallest state's height + top/bottom margins, in design px.
       If content grows taller, raise this number (everything then renders
       slightly smaller at any given window height, to keep fitting).
       Lowering it makes the design bolder but risks clipping tall states.
     - --design-w: panel width (880) + the two 72-unit side margins = 1024.
       Keep it in step if you change the panel width or left offset below.
     - the 1px term inside min() is the CAP: --u never grows past 1px, so
       the design does not inflate on huge monitors. Raise the cap if you
       ever want the whole composition to scale UP as well as down.
     - the 0.42px floor stops text becoming unreadably small on extreme
       windows; below that the very-short fallback near the end of this file
       lets the page scroll instead. Raising the floor means "give up and
       scroll sooner"; lowering it lets the panel shrink further first.
     - --u is declared TWICE on purpose: the svh version (which ignores
       collapsing mobile browser chrome) wins where supported; the vh line
       above it is the fallback for older browsers — not a stray duplicate
       to tidy away. Any change must be made to BOTH lines. */
  --design-h: 1120;
  --design-w: 1024;
  --u: max(0.42px, min(1px, calc(100vh / var(--design-h)), calc(100vw / var(--design-w))));
  --u: max(0.42px, min(1px, calc(100svh / var(--design-h)), calc(100vw / var(--design-w))));
  /* EDIT HERE: the TYPE unit and its 13px floor. Layout scales with --u
     all the way down to 0.42, but type must never get hard to read, so
     every font-size in the ramp multiplies --ut instead: the same unit,
     floored so the SMALLEST step of the ramp (.panel-foot and the legal
     line, 14 units) never renders under 13px. The floor is written as
     13px / 14 so the rule reads literally — if the smallest ramp step
     changes, change the 14 here too. Above the floor (--u > 0.93) type
     and layout scale together exactly as before. The measure caps on
     .topic-summary / .topic-copy ride --ut as well, so line lengths stay
     text-relative when the layout is smaller than the type. */
  --ut: max(calc(13px / 14), var(--u));
  /* --uh is the HEADING unit (.topic-title, .portfolio-title): the same as
     --ut on desktop; the phone block re-points it at --u so headings keep
     scaling with the card and the wordmark stays the heaviest thing on a
     phone (reading type still holds the 13px floor there). */
  --uh: var(--ut);
  /* EDIT HERE: the AIR unit. Because type holds its floor, the vertical
     rhythm has to give way on short screens — but tall screens should keep
     the design's generous spacing. Every vertical-rhythm measurement is
     therefore written in two parts: a COMPACT part that rides --u like
     everything else, plus an EXTRA part that rides --us, which is 0px on
     a 680px-tall viewport (a 1366×768 laptop with browser chrome — every
     state fits exactly there) and grows to a full 1px at 1120px tall —
     the design height, where --u reaches 1 too. So
     "calc(24 * var(--u) + 41 * var(--us))" reads: 24 units always, up to
     41 more as room appears — 65 in the original design. Move the 680 to
     change where compact spacing kicks in; the 440 is the run (680 + 440
     = 1120, the height at which spacing is fully open). A 1440×900
     laptop sits about halfway. Declared twice like --u: the svh line
     wins where supported. */
  --us: clamp(0px, calc((100vh - 680px) / 440), 1px);
  --us: clamp(0px, calc((100svh - 680px) / 440), 1px);
  --inset: calc(24 * var(--u));         /* the universal left/right text inset */
  /* --type-scale is the admin portal's A-/A+ control: content.js writes
     the published design.json value onto the panel as an inline style,
     overriding this 1 (the admin steps it by 0.05, floor 0.8). It scales
     TYPE only, never layout — every font-size below multiplies by it.
     The "var(--type-scale, 1)" spellings further down are the same
     variable with a written-in safety default of 1. */
  --type-scale: 1;
  --type-body: calc(18 * var(--ut) * var(--type-scale));  /* the 18-unit body size all secondary type keys off */
  position: fixed;                      /* desktop: pinned; the page behind never scrolls */
  left: calc(72 * var(--u));            /* gap from the viewport's left edge (72 design px) */
  top: calc(72 * var(--u));             /* gap from the top edge */
  /* EDIT HERE: panel width — 880 design units (keep --design-w above in
     step), with a 660px FLOOR so the 13px-minimum type always has a
     readable measure to wrap in: below --u 0.75 the panel stops shrinking
     while the layout around it still does. 660 is the original design
     width, so nothing ever renders narrower than the site once did. */
  width: max(660px, calc(880 * var(--u)));
  display: flex;
  flex-direction: column;               /* head / topics / foot stack; lets order:-1 lift the active topic */
  color: var(--ink);
  background: var(--glass);
  -webkit-backdrop-filter: blur(65px);  /* Safari spelling of the line below — change both together */
  backdrop-filter: blur(65px);          /* the frost: higher = milkier, lower = sharper gradient showing through */
  border-radius: calc(6 * var(--u));    /* corner rounding, 6 design px */
  z-index: 2;                           /* above the canvases (0), below .bg-switcher (3) */
}
/* EDIT HERE: dark-mode values for the same three variables — white ink,
   slightly brighter hairlines (0.26), and a dark smoked-glass tint (18,17,16
   is a warm near-black; raise the 0.38 opacity for a more solid panel over
   busy backgrounds). */
.panel[data-mode="dark"] {
  --ink: #ffffff;
  --hairline: rgba(255, 255, 255, 0.26);
  --glass: rgba(18, 17, 16, 0.38);
}
/* Fallback for browsers with no backdrop-filter support: the glass tint
   alone is too transparent to keep text readable, so the panel gets a much
   more opaque plain background instead (0.72 light / 0.85 dark — tune
   these independently of the real glass values above). */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .panel { background: rgba(255, 255, 255, 0.72); }
  .panel[data-mode="dark"] { background: rgba(18, 17, 16, 0.85); }
}

/* Links inside the panel inherit the ink colour and carry no underline —
   the layout itself (whole-card hit areas, the arrows) signals what is
   clickable; see the stretched-link rules further down. */
.panel a { color: inherit; text-decoration: none; }

/* A reusable 1px divider (used as an <hr class="hairline"> in the markup);
   its colour comes from the mode variables above. Deliberately a fixed 1px
   rather than design units, so hairlines never go fuzzy or vanish when the
   scale rig shrinks. */
.hairline {
  border: 0;
  border-top: 1px solid var(--hairline);
}

/* ---- Panel head: logo, heading, tagline --------------------------------
   The <h1> in the markup wraps a home link containing the logo <img>, so
   its 24px font-size only ever shows as alt-text if the logo image fails
   to load — the visible wordmark is the image, sized by .logo below. */

/* Head padding: 32 units top / 24 sides (--inset) / 22 bottom, of which
   6 and 4 are air (see --us) — 26 / 18 on short screens. */
.panel-head { padding: calc(26 * var(--u) + 6 * var(--us)) var(--inset) calc(18 * var(--u) + 4 * var(--us)); }
.panel-head h1 { font-size: 24px; font-weight: 400; }  /* alt-text fallback only — see banner */
/* EDIT HERE: tagline size — same as the 18-unit body copy (matches
   .topic-summary, tracking included), riding --type-scale like all the
   type. The 10-unit margin-top is the gap between logo and tagline. */
.panel-head .tagline {
  font-size: var(--type-body);
  letter-spacing: -0.03em;
  margin-top: calc(10 * var(--u));
}

/* EDIT HERE: logo size. The wordmark logo is an image sized as a
   percentage of the panel's width — currently 42% (about 277 design px of
   the 660-unit panel). Change the width value below to scale it (height
   follows automatically from the SVG's own aspect ratio; the flip-cover
   block at the end nudges it on tiny screens).
   Dark mode swaps ARTWORK FILES (no more invert filter): the header holds
   a pair of imgs and exactly one shows. NAMING GOTCHA — the SVGs are named
   for their GLYPH colour, not the mode: *-Logo-dark.svg is the #202020 art
   shown on the LIGHT panel; *-Logo-light.svg is the white art for DARK
   mode. The dark img carries an INLINE style="display:none" in the HTML —
   that is the stale-cache safety net (nothing but the !important rule
   below can reveal it, so old cached CSS can never render two logos; a
   plain hidden attribute would NOT work because .logo's display:block
   out-ranks the browser's [hidden] rule). The !important is what lets the
   dark-mode rule beat the inline style — keep the pair together. */
.logo { display: block; width: 42%; height: auto; }
.panel[data-mode="dark"] .logo-on-light { display: none; }
.panel[data-mode="dark"] .logo-on-dark { display: block !important; }

/* The stack of topic sections between head and foot. A flex column so the
   state machine can lift the active topic to the top with order: -1. */
.panel-body { display: flex; flex-direction: column; }

/* ---- Topics ------------------------------------------------------------
   The big content blocks — Consultancy and Investments on the home page,
   plus About on about.html. Each .topic holds: a large title, a one-line
   summary, an arrow, and a hidden .topic-detail block that only shows
   when that topic is the active state (see the state machine section
   further down). The data-topic attribute on each <section> is what the
   state machine selectors match against. */

.topic {
  display: flex;
  flex-direction: column;
  align-items: flex-start;               /* children hug the left; the arrow keeps its own width */
  border-top: 1px solid var(--hairline); /* the divider line above every topic */
  padding: calc(16 * var(--u) + 6 * var(--us)) var(--inset) calc(16 * var(--u) + 6 * var(--us));  /* 22 top / 24 sides / 22 bottom, 6 of each is air (--us): 16 on short screens */
}

/* EDIT HERE if you want the headline type smaller or larger: 48 design
   units is the expanded topic title size, weight 500 (medium). It drops
   to body size (18) when another topic is active — see the state machine
   section. The -0.03em letter-spacing tightens the large setting.
   Line-height is 1.1em plus up to 12px of air (--us): 1.35 on tall
   screens, 1.1 — tight but safe for the one-word titles — on short ones.
   Rides --type-scale. */
.topic-title {
  font-size: calc(48 * var(--uh) * var(--type-scale, 1));
  font-weight: 400;
  letter-spacing: -0.03em;
  line-height: calc(1.1em + 12 * var(--us));
}
/* inline-block so the stretched-link ::after (further down) positions
   against the topic card rather than collapsing to a text box. */
.topic-title a { display: inline-block; }

.topic-summary {
  font-size: var(--type-body);          /* body size: 18 units × --type-scale */
  letter-spacing: -0.03em;
  max-width: calc(720 * var(--ut));     /* measure cap (text-relative, see --ut) — narrower than the panel so lines stay readable */
  margin-top: calc(8 * var(--u));       /* gap under the title */
}

/* The clickable arrow under each topic — drawn in CSS rather than an image
   so it can GROW. The shaft (::before) is anchored at its left end; the
   chevron head (::after) rides the right edge. On hover (anywhere on the
   topic block — that is the .topic:hover selector at the bottom) the whole
   container widens, the tail stays put and the arrow extends to the right,
   then settles back on mouse-out. It also grows for keyboard users via
   :focus-visible. currentColor means it follows the panel's --ink
   automatically in both light and dark modes.
   EDIT HERE: the hover travel is the width difference — resting 45 units,
   hovered 176, so the arrow grows by 131 design px. The 0.45s
   cubic-bezier(0.22, 1, 0.36, 1) is a fast-out, soft-landing ease: raise
   0.45s for a lazier arrow, lower it for a snappier one. */
.topic-arrow {
  display: block;
  width: calc(45 * var(--u));           /* resting length — the box the glyph fills */
  height: calc(30 * var(--u) + 15 * var(--us));  /* vertical hit area, 45 units with 15 of air (--us); the glyph centres inside it */
  color: inherit;
  margin-top: calc(24 * var(--u) + 41 * var(--us));  /* air between summary text and arrow: the design's 65 units on tall screens, 24 on short ones (--us), fixed 14px on phones */
  transition: width 0.45s cubic-bezier(0.22, 1, 0.36, 1);
}
/* The glyph simply fills whatever size .topic-arrow currently is — that is
   the whole trick: animate the box, and shaft + head follow along. */
.arrow-glyph { position: relative; display: block; width: 100%; height: 100%; }
.arrow-glyph::before {              /* the shaft — left end never moves */
  content: '';
  position: absolute;
  left: 0;
  right: 4px;                       /* stop 4px short so the shaft never pokes past the chevron */
  top: 50%;
  height: 3px;                      /* stroke weight — keep in step with the chevron borders below */
  background: currentColor;
  transform: translateY(-50%);      /* true vertical centring on the 50% line */
}
.arrow-glyph::after {               /* the chevron head, riding the tip */
  content: '';
  position: absolute;
  right: 2px;                       /* tucks the head onto the shaft's end */
  top: 50%;
  width: calc(14 * var(--u));       /* chevron size — keep width and height equal */
  height: calc(14 * var(--u));
  border-top: 3px solid currentColor;    /* stroke weight — match the shaft height above */
  border-right: 3px solid currentColor;
  transform: translateY(-50%) rotate(45deg);  /* two borders turned 45° = a chevron */
}
/* The grow triggers: hovering anywhere on the topic card, hovering the
   arrow itself, or keyboard focus. 176 units is the hovered length — the
   "EDIT HERE" travel described above. */
.topic:hover .topic-arrow,
.topic-arrow:hover,
.topic-arrow:focus-visible { width: calc(176 * var(--u)); }

/* The WHOLE topic block is the button wherever it navigates (home state and
   the collapsed rows): the title link stretches invisibly across the full
   section, so a click anywhere in the space goes through. Mechanically:
   the .topic gets position: relative (first group), then the title link
   grows an ::after pseudo-element stretched to inset: 0 — i.e. to the
   .topic's edges (second group). Nothing visible is added; the link's hit
   area simply becomes the card. The active topic is exempt so its body
   text stays selectable.

   The two selector lists are the same state × topic pairs: in "home",
   every topic is a link; in any active state, every NON-active topic is.
   If you ever add a topic or state, both lists need the new combinations
   (see the state machine banner below for the pattern). */
[data-state="home"] .topic,
[data-state="consultancy"] .topic[data-topic="investments"],
[data-state="investments"] .topic[data-topic="consultancy"],
[data-state="consultancy"] .topic[data-topic="about"],
[data-state="investments"] .topic[data-topic="about"],
[data-state="about"] .topic[data-topic="consultancy"],
[data-state="about"] .topic[data-topic="investments"] {
  position: relative;
  cursor: pointer;
}
[data-state="home"] .topic .topic-title a::after,
[data-state="consultancy"] .topic[data-topic="investments"] .topic-title a::after,
[data-state="investments"] .topic[data-topic="consultancy"] .topic-title a::after,
[data-state="consultancy"] .topic[data-topic="about"] .topic-title a::after,
[data-state="investments"] .topic[data-topic="about"] .topic-title a::after,
[data-state="about"] .topic[data-topic="consultancy"] .topic-title a::after,
[data-state="about"] .topic[data-topic="investments"] .topic-title a::after {
  content: '';
  position: absolute;
  inset: 0;
}

/* Detail content is hidden by default; the state machine below reveals it
   for whichever topic is active. width: 100% so hairlines and lists inside
   span the full panel, not just the text width. */
.topic-detail { display: none; width: 100%; }

/* Long-form reading copy inside a detail block: 15.6 design units — about
   13% under the 18-unit body — capped at a 640-unit measure. Rides
   --type-scale. The 8-unit margin is the gap above each paragraph. */
.topic-copy {
  font-size: calc(15.6 * var(--ut) * var(--type-scale, 1));
  max-width: calc(640 * var(--ut));     /* measure cap, text-relative: about 80 characters at this size */
  margin-top: calc(8 * var(--u));
}

/* The hairline above the portfolio block — its 140-unit margin is the big
   breathing space between the reading copy and the list that follows. */
.detail-rule { margin-top: calc(140 * var(--u)); }

/* EDIT HERE: portfolio heading — 36 design units, weight 400 (matches the
   topic titles), the same tight -0.03em tracking; 16 units under the
   hairline above it. Rides --type-scale. */
.portfolio-title {
  font-size: calc(36 * var(--uh) * var(--type-scale, 1));
  font-weight: 400;
  letter-spacing: -0.03em;
  margin-top: calc(16 * var(--u));
}

/* The company list: same 15.6 size as the reading copy, bullets stripped,
   22 units under the portfolio heading. */
.portfolio-list {
  list-style: none;
  font-size: calc(15.6 * var(--ut) * var(--type-scale, 1));
  margin-top: calc(22 * var(--u));
}

/* ---- State machine -----------------------------------------------------
   The panel has four states: "home" (all topics shown large), or one of
   "consultancy" / "investments" / "about" (that topic expands, the others
   collapse to compact link rows). The state lives in the data-state
   attribute on .panel, which home.js sets from the URL hash (#consultancy
   / #investments / #about) — so the back button, refresh and deep links
   all keep the state.

   Everything below is pure CSS reacting to that attribute; there is no
   show/hide JavaScript to edit. The topic <h2> is the same DOM node in
   every state (48 units expanded, body-size 18 collapsed) so a later FLIP
   pass can animate it directly.

   HOW TO READ THE SELECTOR GROUPS: every selector pairs "which state are
   we in" ([data-state="…"]) with "which topic does this rule affect"
   (.topic[data-topic="…"]). The ACTIVE groups list one selector per
   active state (3 lines); the COLLAPSED groups list every state ×
   other-topic combination (6 lines). If you add a fourth topic, every
   group below — and the stretched-link lists above — needs its new
   combinations, and home.js's hash whitelist needs the new name. */

/* Active topic: full detail visible, no summary, no arrow, and ordered
   first (order: -1 beats the default 0 of its siblings) so it sits at the
   top of the panel regardless of its position in the markup. */
[data-state="consultancy"] .topic[data-topic="consultancy"],
[data-state="investments"] .topic[data-topic="investments"],
[data-state="about"] .topic[data-topic="about"] {
  order: -1;
}
/* …its one-line summary and its arrow disappear (the detail replaces them)… */
[data-state="consultancy"] .topic[data-topic="consultancy"] .topic-summary,
[data-state="investments"] .topic[data-topic="investments"] .topic-summary,
[data-state="about"] .topic[data-topic="about"] .topic-summary,
[data-state="consultancy"] .topic[data-topic="consultancy"] .topic-arrow,
[data-state="investments"] .topic[data-topic="investments"] .topic-arrow,
[data-state="about"] .topic[data-topic="about"] .topic-arrow {
  display: none;
}
/* …and its hidden detail block becomes the visible content. */
[data-state="consultancy"] .topic[data-topic="consultancy"] .topic-detail,
[data-state="investments"] .topic[data-topic="investments"] .topic-detail,
[data-state="about"] .topic[data-topic="about"] .topic-detail {
  display: block;
}

/* Collapsed topics (the OTHER ones): compact link rows below the active
   topic, above the footer — small title on the left, small arrow on the
   right. flex-direction flips from column to row and space-between pushes
   the two ends apart; padding stays 22 units vertical / 24 side. */
[data-state="consultancy"] .topic[data-topic="investments"],
[data-state="investments"] .topic[data-topic="consultancy"],
[data-state="consultancy"] .topic[data-topic="about"],
[data-state="investments"] .topic[data-topic="about"],
[data-state="about"] .topic[data-topic="consultancy"],
[data-state="about"] .topic[data-topic="investments"] {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  padding: calc(22 * var(--u)) var(--inset);
}
/* Collapsed title: drops from 48 units to body size (18) and loses the
   large-setting refinements (tight tracking, tall line-height). */
[data-state="consultancy"] .topic[data-topic="investments"] .topic-title,
[data-state="investments"] .topic[data-topic="consultancy"] .topic-title,
[data-state="consultancy"] .topic[data-topic="about"] .topic-title,
[data-state="investments"] .topic[data-topic="about"] .topic-title,
[data-state="about"] .topic[data-topic="consultancy"] .topic-title,
[data-state="about"] .topic[data-topic="investments"] .topic-title {
  font-size: var(--type-body);
  letter-spacing: 0;
  line-height: normal;
}
/* A collapsed row shows ONLY title + arrow: summary and detail are off. */
[data-state="consultancy"] .topic[data-topic="investments"] .topic-summary,
[data-state="investments"] .topic[data-topic="consultancy"] .topic-summary,
[data-state="consultancy"] .topic[data-topic="about"] .topic-summary,
[data-state="investments"] .topic[data-topic="about"] .topic-summary,
[data-state="about"] .topic[data-topic="consultancy"] .topic-summary,
[data-state="about"] .topic[data-topic="investments"] .topic-summary,
[data-state="consultancy"] .topic[data-topic="investments"] .topic-detail,
[data-state="investments"] .topic[data-topic="consultancy"] .topic-detail,
[data-state="consultancy"] .topic[data-topic="about"] .topic-detail,
[data-state="investments"] .topic[data-topic="about"] .topic-detail,
[data-state="about"] .topic[data-topic="consultancy"] .topic-detail,
[data-state="about"] .topic[data-topic="investments"] .topic-detail {
  display: none;
}
/* Collapsed arrow: a 25-unit box instead of 45, flush with the row (the
   72-unit desktop margin is cleared so the arrow centres on the title). */
[data-state="consultancy"] .topic[data-topic="investments"] .topic-arrow,
[data-state="investments"] .topic[data-topic="consultancy"] .topic-arrow,
[data-state="consultancy"] .topic[data-topic="about"] .topic-arrow,
[data-state="investments"] .topic[data-topic="about"] .topic-arrow,
[data-state="about"] .topic[data-topic="consultancy"] .topic-arrow,
[data-state="about"] .topic[data-topic="investments"] .topic-arrow {
  width: calc(25 * var(--u));
  height: calc(25 * var(--u));
  margin: 0;
}
/* Slimmer shaft for the compact arrow: 2px stroke (the big arrows use
   3px), stopping 3px short of the tip. */
[data-state="consultancy"] .topic[data-topic="investments"] .arrow-glyph::before,
[data-state="investments"] .topic[data-topic="consultancy"] .arrow-glyph::before,
[data-state="consultancy"] .topic[data-topic="about"] .arrow-glyph::before,
[data-state="investments"] .topic[data-topic="about"] .arrow-glyph::before,
[data-state="about"] .topic[data-topic="consultancy"] .arrow-glyph::before,
[data-state="about"] .topic[data-topic="investments"] .arrow-glyph::before {
  height: 2px;
  right: 3px;
}
/* Smaller chevron to match: 9 units square with 2px strokes, tucked 1px
   from the right edge so it sits on the thinner shaft. */
[data-state="consultancy"] .topic[data-topic="investments"] .arrow-glyph::after,
[data-state="investments"] .topic[data-topic="consultancy"] .arrow-glyph::after,
[data-state="consultancy"] .topic[data-topic="about"] .arrow-glyph::after,
[data-state="investments"] .topic[data-topic="about"] .arrow-glyph::after,
[data-state="about"] .topic[data-topic="consultancy"] .arrow-glyph::after,
[data-state="about"] .topic[data-topic="investments"] .arrow-glyph::after {
  width: calc(9 * var(--u));
  height: calc(9 * var(--u));
  border-top-width: 2px;
  border-right-width: 2px;
  right: 1px;
}
/* Shorter hover travel for the compact rows: 25 → 88 units (the big
   arrows go 45 → 176). */
[data-state="consultancy"] .topic[data-topic="investments"]:hover .topic-arrow,
[data-state="investments"] .topic[data-topic="consultancy"]:hover .topic-arrow,
[data-state="consultancy"] .topic[data-topic="about"]:hover .topic-arrow,
[data-state="investments"] .topic[data-topic="about"]:hover .topic-arrow,
[data-state="about"] .topic[data-topic="consultancy"]:hover .topic-arrow,
[data-state="about"] .topic[data-topic="investments"]:hover .topic-arrow {
  width: calc(88 * var(--u));
}

/* ---- Footer ------------------------------------------------------------
   The bottom strip of the panel: a row of links (.foot-links — Get In
   Touch / LinkedIn / Substack, space-between spreading them to the panel
   edges, aligned on the text baseline) with the company registration
   line (.foot-legal) underneath. General Sans like everything else (it
   briefly used Hanken Grotesk; Hanken stays registered in fonts.css only
   for the legacy pages).
   EDIT HERE: the legal line's wording lives in the HTML (data-cms
   "foot-legal", so the admin portal can edit it too). It is deliberately
   plain text — the two companies are NOT linked yet; when they should be,
   wrap the names in <a>s in the page files. */

.panel-foot {
  border-top: 1px solid var(--hairline);
  padding: calc(14 * var(--u) + 2 * var(--us)) var(--inset) calc(18 * var(--u) + 6 * var(--us));  /* 16 top / 24 sides / 24 bottom on tall screens; 14 / 18 on short ones (--us) */
  font-family: 'General Sans', sans-serif;
  font-size: calc(14 * var(--ut) * var(--type-scale, 1));  /* the smallest step of the type ramp — 13px at the floor */
}
.foot-links {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
/* The registration line: same 14-unit size as the links, knocked back to
   0.7 opacity so it reads as small print, 16 units under the links row
   (12 on short screens — the rest is air, --us). */
.foot-legal {
  margin-top: calc(12 * var(--u) + 4 * var(--us));
  opacity: 0.7;
  line-height: 1.4;
}

/* ---- Background switcher -----------------------------------------------
   The prev/next control fixed to the bottom-right of the viewport (outside
   the panel). home.js wires the two round arrow buttons to cycle through
   the palette registry — the presets in assets/palettes.js merged with any
   gradients saved from the builder (localStorage key
   'higgins.savedGradients'). .bg-name is the label showing the current
   background's name.

   Note the text colour is a fixed near-black — it does NOT follow the
   panel's light/dark mode, so it can be hard to read over very dark
   backgrounds. Change the color value below if that becomes a problem. */

.bg-switcher {
  position: fixed;
  right: 26px;                       /* fixed px, not design units — the switcher never scales */
  bottom: 22px;
  z-index: 3;                        /* above the panel (2) and the canvases (0) */
  display: flex;
  align-items: center;
  gap: 12px;                         /* space between prev button, name label, next button */
  color: #202020;
  font-size: 12px;
  letter-spacing: 0.14em;            /* wide tracking for the small uppercase label */
  text-transform: uppercase;
}

/* The current background's name (written by home.js). min-width stops the
   buttons jumping about as short and long names swap; 0.7 opacity knocks
   the label back behind the buttons. */
.bg-name {
  min-width: 64px;
  text-align: center;
  opacity: 0.7;
}

/* The round prev/next buttons: 32px circles (border-radius 999px just
   means "fully round"). They idle at 0.6 opacity and pop to full with a
   slight 1.08 grow on hover or keyboard focus, over 0.2s. */
.bg-switcher .arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1px solid currentColor;
  border-radius: 999px;
  background: none;                  /* strip the native button chrome */
  color: inherit;
  font: inherit;                     /* buttons don't inherit fonts by default */
  font-size: 14px;                   /* the ‹ › glyph size */
  letter-spacing: 0;                 /* undo the parent's 0.14em for the glyph */
  cursor: pointer;
  opacity: 0.6;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.bg-switcher .arrow:hover,
.bg-switcher .arrow:focus-visible { opacity: 1; transform: scale(1.08); }

/* ---- Motion ------------------------------------------------------------
   Three pieces, all driven by classes that home.js (and the builder's JS)
   toggle on .panel. This file only describes the states; the SEQUENCING —
   when each class is added and removed — lives in home.js, so timing
   changes usually mean touching both files:

   1. Mode fade — the .panel transition just below cross-fades glass and
      ink over 0.35s whenever data-mode flips (builder control, or an
      admin publish applied by content.js).
   2. Load-in choreography (.intro / .intro-grow) — on first load home.js
      adds .intro (logo wipes in, everything else sits hidden), then
      .intro-grow (the sections stagger in while the panel grows down the
      page), then removes both. The panel height itself is animated by
      home.js with explicit pixel values, not by CSS here. Background-tab
      loads skip the intro entirely (hidden tabs get throttled timers, so
      it would play out half-broken).
   3. State transitions (.leaving / .entering / .entered) — on hash change
      home.js adds .leaving (old content fades up, 180ms), swaps
      data-state, then adds .entering (new content parks below) and, one
      frame later, .entered (it rides in) while the height animates.

   Users with prefers-reduced-motion never get these classes (the scripts
   check), and a safety rule below also stills everything for them. */

/* Piece 1: the 0.35s mode cross-fade — lengthen both values for a slower
   light/dark morph (keep them equal so glass and ink move together). */
.panel {
  transition: background-color 0.35s ease, color 0.35s ease;
}

/* Load-in: logo fades in first — a 0.9s wipe starting 0.15s after .intro
   lands ("forwards" holds the final frame so it can't snap back). */
.panel.intro .logo {
  opacity: 0;
  animation: logo-in 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.15s forwards;
}
/* The wipe itself: clip-path un-crops the logo left→right (inset's second
   value shrinking 100%→0) while it slides in from 18px left of home. */
@keyframes logo-in {
  from { opacity: 0; clip-path: inset(0 100% 0 0); transform: translateX(-18px); }
  to   { opacity: 1; clip-path: inset(0 0 0 0); transform: none; }
}

/* …everything below the logo waits, hidden and pushed 12px down… */
.panel.intro .tagline,
.panel.intro .panel-body > *,
.panel.intro .panel-foot {
  opacity: 0;
  transform: translateY(12px);
}

/* …then rides in on a stagger as the panel grows: each element fades over
   0.55s and settles over 0.6s, offset by the transition-delays below —
   tagline first at 0.1s, then the topics at 0.25s / 0.45s, footer last at
   0.62s. Stretch the delays apart for a lazier build; note the
   :nth-child delays go by POSITION in .panel-body, not by topic name. */
.panel.intro-grow .tagline,
.panel.intro-grow .panel-body > *,
.panel.intro-grow .panel-foot {
  opacity: 1;
  transform: none;
  transition: opacity 0.55s ease, transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.panel.intro-grow .tagline { transition-delay: 0.1s; }
.panel.intro-grow .panel-body > *:nth-child(1) { transition-delay: 0.25s; }
.panel.intro-grow .panel-body > *:nth-child(2) { transition-delay: 0.45s; }
.panel.intro-grow .panel-foot { transition-delay: 0.62s; }

/* State change, phase 1 (.leaving): current content fades and pushes up
   10px in a quick 0.18s — this matches the 180ms timer in home.js, so
   change the two together or the swap will cut the fade short. */
.panel.leaving .panel-body,
.panel.leaving .panel-foot {
  opacity: 0;
  transform: translateY(-10px);
  transition: opacity 0.18s ease, transform 0.18s ease;
}

/* …phase 2 (.entering): incoming content parks hidden 14px below its
   resting spot — no transition here on purpose; it must jump instantly. */
.panel.entering .panel-body,
.panel.entering .panel-foot {
  opacity: 0;
  transform: translateY(14px);
}

/* …phase 3 (.entering.entered): fades in over 0.28s and settles over
   0.3s, both after a 0.06s beat so the height change reads first. */
.panel.entering.entered .panel-body,
.panel.entering.entered .panel-foot {
  opacity: 1;
  transform: none;
  transition: opacity 0.28s ease 0.06s, transform 0.3s cubic-bezier(0.22, 1, 0.36, 1) 0.06s;
}

/* Belt-and-braces: even if a motion class slips through, reduced-motion
   users see no movement — every transition and animation in the panel is
   forcibly stilled. */
@media (prefers-reduced-motion: reduce) {
  .panel, .panel *, .topic-arrow { transition: none !important; animation: none !important; }
}

/* ---- Responsive layouts ------------------------------------------------
   Four layouts on top of the desktop default, chosen by width and (for the
   flip-phone cases) aspect ratio:

   - desktop  (>=1200px)          fixed 72/72, the 660-unit panel (the
                                  default rules above — nothing extra here)
   - iPad     (768–1199px)        the same panel, centred horizontally;
                                  the scale rig handles the sizing
   - phones   (<768px, incl. flip phones open)
                                  an in-flow card with an 8px gradient
                                  frame all round; the page scrolls; the
                                  gradient stays fixed behind
   - flip cover (<=480px wide, small and near-square)
                                  full-bleed with a compact type scale
   plus a safety valve for very short desktop windows at the very end.
   EDIT HERE: breakpoint numbers and the flip-cover treatment (swap the
   margin/width lines to re-centre it instead of full-bleed). */

/* iPad and mid-size windows: centred; the scale rig already sizes the
   panel to fit, this block only moves it to the middle — left/right 0
   plus margin-inline auto centres a fixed-position box; the 72-unit top
   offset still applies. */
@media (max-width: 1199px) and (min-width: 768px) {
  .panel {
    left: 0;
    right: 0;
    margin-inline: auto;
  }
}

/* Phones: the panel becomes an in-flow card with 8px of gradient visible
   all the way around it, and the page scrolls (the canvases are fixed, so
   the gradient never scrolls away behind the card). */
@media (max-width: 767px) {
  body { overflow: auto; }             /* the one place desktop's no-scroll rule lifts on phones */
  .panel {
    /* The page scrolls here, so height never constrains the type: pin the
       scale unit to a comfortable fixed size instead of the viewport rig.
       EDIT HERE: 0.72px is the master phone-size knob — every design-unit
       measurement (type, insets, arrows) renders at 72% of desktop.
       Nudge it up or down to scale the entire phone layout at once. */
    --u: 0.72px;
    /* Headings follow the card scale here (--u) rather than the floored
       type unit, so the wordmark stays visibly heavier than the section
       titles on a phone — the weighting Jake approved. Reading type
       (--ut) keeps its 13px floor regardless. */
    --uh: var(--u);
    position: relative;                /* back into the document flow so it can scroll */
    left: auto;                        /* clear the desktop 72-unit offsets */
    top: auto;
    margin: 8px;                       /* the gradient frame around the card */
    width: auto;                       /* full width minus those margins */
    min-height: calc(100vh - 16px);    /* fill the screen (16 = 2 × the 8px margin); vh fallback… */
    min-height: calc(100svh - 16px);   /* …svh wins where supported (ignores collapsing browser chrome) */
    border-radius: 6px;                /* fixed 6px, echoing desktop's 6 design units */
  }
  /* Tighter vertical rhythm so all three cards + footer fit one screen:
     arrows sit 16px under the summaries instead of desktop's 72 units. */
  .topic-arrow { margin-top: 14px; }
  /* The wordmark takes a bigger share of the narrow card (50% vs 42% on
     desktop) so it still out-weighs the floored 13px-minimum reading
     type underneath it — the phone hierarchy Jake signed off. */
  .logo { width: 50%; }
  .panel-body { flex: 1 1 auto; }      /* body stretches, keeping the footer pinned at the card's foot */
  .topic-summary, .topic-copy { max-width: 100%; }  /* release the desktop measure caps */
  .bg-switcher { right: 10px; bottom: 10px; gap: 8px; }  /* tuck the switcher tighter into the corner */
  .bg-switcher .arrow { width: 26px; height: 26px; font-size: 12px; }  /* smaller round buttons */
  .bg-name { min-width: 48px; }        /* narrower slot for the name label */
}


/* Flip phone COVER screen: small and near-square — at most 480px wide and
   500px tall, with an aspect ratio no narrower than 4:5 (so tall ordinary
   phones don't match). Fill it completely and drop the type a step so the
   essentials read at a glance. This layers ON TOP of the phone block
   above, overriding only what's listed here. */
@media (max-width: 480px) and (max-height: 500px) and (min-aspect-ratio: 4/5) {
  .panel {
    --u: 0.55px;                       /* even smaller scale unit: 55% of desktop */
    margin: 0;                         /* full-bleed: no gradient frame… */
    width: 100%;
    min-height: 100vh;                 /* …and fill the whole cover screen (svh wins below) */
    min-height: 100svh;
    border-radius: 0;                  /* square corners against the screen edges */
  }
  .logo { width: 34%; }                /* fractionally bigger logo share on the tiny canvas */
}

/* Safety valve: because type holds its 13px floor (--ut) while the layout
   keeps shrinking, the tallest state — Roles (its 140-unit detail rule)
   and, close behind, home — stops fitting in desktop-width windows under
   about 660px tall. That is below a 1366×768 laptop with browser chrome
   showing (~680), which was the target: every state fits there. Below
   660 the page scrolls instead of clipping the footer out of reach.
   min-width 768 keeps this from fighting the phone layout above; the
   panel drops back into the flow with the SAME 72-unit margins it has
   when fixed, so nothing shifts between the two modes — the page merely
   becomes scrollable. EDIT HERE: if the tallest state grows (a fourth
   card, a longer bio) raise the 660 — the probe is: shrink the window
   until the footer clips, and set the valve just above that height. */
@media (min-width: 768px) and (max-height: 660px) {
  body { overflow: auto; }
  .panel {
    position: relative;
    left: auto;
    top: auto;
    margin: calc(72 * var(--u)) auto calc(72 * var(--u)) calc(72 * var(--u));
  }
}
/* …and on the centred mid-width layout the scrolling panel stays centred. */
@media (min-width: 768px) and (max-width: 1199px) and (max-height: 660px) {
  .panel { margin-inline: auto; }
}
