/* =========================================================================
   INTRO-1 SITE FOOTER — index.html ONLY.  v=24

   ISOLATED SHEET, per the INTRO-1 contract: loaded only by index.html,
   so the portfolio index-portfolio-archived.html, INTRO-3 and the v2/v3 prototypes are untouched.
   Nothing here overrides a shared rule — every selector is new.

   Reference: the footer on pellmell.fr, read as a COMPOSITION GUIDE ONLY —
   nothing traced, nothing placed as an image.

   NOT a full-bleed band. The footer is an ISLAND that matches the project row
   containers: 1205.48px wide, centred, on a 100vh screen — the same wrapper shape
   .ef-project-screen / .se-project-screen / .bw-project-screen use, so this reads
   as one more island in the same rhythm rather than as a band across the page.

   *** 1276 -> 1205.48 (user, 2026-08-25). THIS SUPERSEDES THE 2026-08-24 LOCK'S
   "1276px wide", WHICH WAS MEASURING THE WRONG BOX. *** Every themed
   .project-row--live IS 1276 — but it is `background: transparent`, as is its
   .visual-panel, and neither paints anything. The visible project container is the
   screen INSIDE that frame (.bw-screen / .se-screen / .ef-stack), which paints
   1205.48 x 660 at left: 35.26 — so the footer at a true 1276 stood 35.26px proud
   on EACH side of the card above it. Matching the frame is not matching the edge.
   MEASURED AT 1440 (doc 1425, scrollbar-narrowed), feather-land cleared:
       .project-row--live frame   74.50 -> 1350.50   1276.00   paints nothing
       .bw-screen (PAINTED)      109.75 -> 1315.23   1205.48   the visible edge
       .pi-footer, was           74.50 -> 1350.50   1276.00   +35.25 per side
       .pi-footer, now           109.75 -> 1315.23   1205.48   flush
   THE SAME CORRECTION WAS ALREADY MADE ONCE, to the hero card: .hero-project went
   1276 -> 1203.973 on 2026-07-28 for this exact reason ("that element is
   background: transparent and paints NOTHING"). The footer simply did not follow.
   AND THE "1276 = the Matey #heroCard size" PROVENANCE CITED HERE IS RETRACTED —
   css/pellmell-intro-lipscale.css records it as a sampling error: 89.58% of the
   scrollbar-narrowed 1425 is 1276.5, so 1276 was a measurement of the viewport,
   never an authored card size. 1276x660 survives as the project rows' LAYOUT
   frame; it is not a painted standard and nothing should be sized to it. ***

   COLOURS ARE ALL EXISTING — nothing new introduced:
     ground   #141d1f  the build's charcoal (css/pellmell-intro.css .hero-title)
     bullet   #FF3567  the build's red (the most-used red across the INTRO-1 sheets)
     text     #FFFFFF  PURE WHITE (user, 2026-08-24) — tagline, contact link, LINKEDIN.
                       Supersedes #E6F8E9, the build's mint-tinted light-on-dark, which
                       these three carried through the first build. Not a tint of it.
     wordmark #000000  black — DARKER than the ground, so it reads as a void. UNCHANGED;
                       the white above does not touch it.

   STATIC by brief: desktop only, no animation, and NO hover states on either
   link. The hover motion is a later pass — missing on purpose, not an omission.
   ========================================================================= */

/* Screen wrapper — same shape as the other project screens: opaque so it occludes
   the pinned .hero scrolling under it, z-index above that hero, flush to the top of
   its own viewport. No `html:not(.js-loading)` margin: the 100vh push the other
   screens carry exists to clear the pinned hero, and this one follows a normal-flow
   screen rather than the hero, so it needs none. */
.pi-footer-screen {
  position: relative;
  z-index: 200;
  min-height: 100vh;
  background: #ffffff;
  display: flex;
  align-items: flex-start;
  justify-content: center;
}

/* The island. Width and alignment match the project rows' PAINTED box exactly
   (1205.48, centred) — see the width note in the header above.
     1205.48 = .bw-screen  1095.8909 x 1.1       (Brandip, the row directly above)
             = .ef-screen/.se-screen  800 x 1.50685  (EF and 7-Eleven)
   Both derivations land on the same number, which is why the three project screens
   already paint on identical margins; this puts the footer on them too.
   MANUAL-SYNC HAZARD, the same one css/pellmell-intro-lipscale.css carries: there is
   no live link. If .bw-screen's 1095.8909 / 1.1 or the EF stack's 800 / 1.50685 move,
   this number must move with them.

   `max-width: 100%` IS LOAD-BEARING BELOW 1276, and it is what aligns the two boxes
   at the narrow breakpoints rather than a media query — there is no width breakpoint
   anywhere in this sheet or in the three project sheets, by design. The project rows
   cannot shrink (.project-row--live .visual-panel is `flex-shrink: 0`, which gives the
   row an automatic min-width of 1276), so below that they overflow their flex-centred
   screen symmetrically and html/body `overflow-x: hidden` clips them to the viewport —
   i.e. the card reads FULL-BLEED. The footer's content is compressible, so it shrinks
   to the viewport and reads full-bleed too. At 1205.48 the two crossovers now coincide:
   both are 1205.48 centred while the viewport can hold it, and both are edge-to-edge
   once it cannot. MEASURED, footer painted vs card visible extent:
       1440   109.75 -> 1315.23   vs   109.75 -> 1315.23    flush
       1024        0 -> 1009      vs        0 -> 1009       both full-bleed
        768        0 ->  753      vs        0 ->  753       both full-bleed
        430        0 ->  415      vs        0 ->  415       both full-bleed
   DO NOT "fix" this with `flex-shrink: 0` to make the footer as rigid as the rows.
   The rows clip decorative artwork; the footer would clip the mailto link, the
   LINKEDIN link and the wordmark, and it would break the ≤1024 alignment above.

   CORNERS ARE 10px — read off the reference rather than picked: pellmell.fr's own
   footer container is `.AppFooter-wrapper { border-radius: var(--radius-10, 10px) }`.
   This deliberately does NOT match the project rows, which render square (the base
   sheet's 16px corners are overridden to 0 by every themed .project-row--live). The
   brief matched the rows on WIDTH and ALIGNMENT and the reference on the corners.
   64px of padding INSIDE so no content sits on the container edge: the wordmark
   spans 1205.48 - 128 = 1077.48, which is 256.58 tall at the ink ratio
   232.38/975.70 (it was 1148 -> 273.4 at the old 1276) — inset well clear of the
   corners, so no overflow clip is needed to protect them. */
.pi-footer {
  width: 1205.48px;
  max-width: 100%;
  background: #141d1f;
  border-radius: 10px;
  padding: 64px;
  display: flex;
  flex-direction: column;
}

/* ── TWO ROWS ───────────────────────────────────────────────────────────────
   ROW 1  the head row: tagline + mark on ONE flex line, then the two links under it
   ROW 2  the wordmark, spanning under everything

   THE MARK SHARES THE TAGLINE'S LINE, BY CONSTRUCTION (user, 2026-08-24).
   It used to be a sibling of the whole copy block under `align-items: flex-start`, so
   its box was pinned to the block's TOP and it only LOOKED centred on "Get in touch!"
   because two heights happened to nearly agree — the tagline's 44px line box against the
   mark's 42.047px. Measured, the ink centres sat 0.5px apart: coincidence, not intent,
   and it would have drifted silently the next time the tagline's size or line-height
   moved. Now the mark is a flex item on the tagline's OWN line under
   `align-items: center`, so it tracks that line box whatever the type does.
   MEASURED, AT 1440: the mark's box went 64..106 -> 65..107 and its ink 64..104 -> 65..105.
   One pixel down, which is why the footer looks identical either side of this change.
   WHAT REMAINS, AND WHY IT IS NOT WORTH CHASING: `center` centres the mark on the LINE
   BOX, while the eye reads the INK. The tagline's ink sits 1.5px above its line-box
   centre (box 64..108, ink 71..98 - leading is split evenly, but cap-to-baseline is not
   centred within it), and the mark's own ink sits ~0.4px above its box centre
   (--mark-ink-top 0.059 + --mark-ink-h 41.115 against a 42.047 box). So the ink centres
   land ~1px apart in theory; against painted pixels the gap reads as 0.5px, the same
   half-pixel that was there before, with the sign flipped. Correcting it would mean
   hard-coding a font metric here - exactly the kind of coincidence this change removes. */
.pi-footer__top {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 100%;
}

.pi-footer__headrow {
  display: flex;
  align-items: center;          /* the mark rides the tagline's line box */
  justify-content: space-between;
  width: 100%;
}

/* The mark, sized off the build's OWN tokens (css/pellmell-intro.css :root, Figma
   1480:1482) rather than a number invented here — the footer mark and the hero mark
   are the same mark and must not drift. Fallbacks match those tokens' current values.
   NO disc: the T1 lock's 42x42 #141D1F circle is this ground's own colour. The asset's
   path is fill="white", so it reads on the charcoal as-is. */
.pi-footer__mark {
  flex: none;
  display: block;
  width:  var(--mark-w, 22.627px);
  height: var(--mark-h, 42.047px);
  /* BUTTON RESET — the mark is a control now (see the markup note). Everything here
     only undoes UA button styling; nothing changes how the mark paints, so the footer
     looks exactly as it did when this was a span. */
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  line-height: 0;
  -webkit-appearance: none;
          appearance: none;
  cursor: pointer;
}
/* Keyboard users get a ring; mouse users do not. :focus-visible rather than :focus so
   the custom cursor's own affordance is not doubled up on click. */
.pi-footer__mark:focus-visible {
  outline: 2px solid #FF3567;
  outline-offset: 4px;
  border-radius: 2px;
}
.pi-footer__mark img {
  display: block;
  width: 100%;
  height: 100%;
}

/* THE CLOSING ELEMENT — last in the footer, below the copy and the social link, as on
   the reference: their .AppFooter-logo is `width: 100%` and follows the content, with
   `margin-top: calc(var(--spacing,.1rem) * 110)` = 11rem = 110px at landscape (60 at
   portrait). That 110 is the gap above it here. */
.pi-footer__wordmark {
  display: block;
  width: 100%;
  height: auto;
  margin-top: 110px;
}

/* ── TYPEFACE ───────────────────────────────────────────────────────────────
   Bricolage Grotesque, the build's display face (self-hosted, variable 200-800; the
   same stack the Brandip screens use). It was 'Inter' here from this sheet's first
   version until 2026-08-24 — measured, not assumed: "Get in touch!" rendered 243.23px,
   which is Inter's width exactly, against 232.00 for Bricolage. Corrected at the user's
   call; the typeface is Bricolage and does not change.
   NOT changed with it: the "Shoon" wordmark below still draws in Inter 900, because its
   four geometry numbers are DERIVED FROM INTER'S METRICS (advance 6584/2048, the 58 and
   102 side bearings, baseline 229.34). Moving it to Bricolage means re-deriving all four
   or the fit silently breaks — it is a separate job, flagged rather than half-done.

   ── THE LINE — size READ OFF pellmell.fr, not eyeballed ───────────────────
   Their root is fluid: `html { --size: 1440; font-size: calc(1000/var(--size)*1vw) }`
   under `@media (orientation: landscape)`, so at a 1440 viewport 1rem resolves to
   exactly 10px and every --text token below is a literal px at our design width.
   (Theirs then scale with the viewport and ours do not — the two agree at 1440,
   which is the width this build is authored to.)

     .AppFooter-contentText  var(--text-40, 4rem)  = 40px   <- THE line
     .AppButton-title        var(--text-18, 1.8rem)= 18px      (the contact link below)
     .AppFooter-socials      13px                              (their social list)
     .AppFooter-credits      10px

   Line-height 1.1 is their root value (`html { line-height: 1.1 }`) — tight, which is
   what display type at 40px wants.

   ONE line of copy, not two. It maps to their .AppFooter-contentText and, like that
   element, sits DIRECTLY above the contact link — so the 20px clearing it is their own
   .AppFooter-contentText margin-bottom, calc(var(--spacing,.1rem) * 20) = 2rem = 20px
   at desktop, and it now separates exactly the pair it separates on their page. That
   20px lives on .pi-footer__contact's margin-top.
   ────────────────────────────────────────────────────────────────────────── */
.pi-footer__tagline {
  margin: 0;            /* first child now — the wordmark moved below the copy */
  font-family: 'Bricolage Grotesque', 'Lexend', sans-serif;
  /* 300 -> 500 (user, 2026-08-24): match pellmell.fr's RENDERED tagline weight.
     Their rule declares no font-weight, so it computes 400 — but `season` ships as one
     static Medium file, and DevTools Rendered Fonts shows it painting
     SeasonSansTRIAL-Medium. The contact link below takes the SAME 500 for the same
     reason (it declares 650 and paints the identical face). Inter is variable 100-900,
     so 500 is a real instance, not a synthesised weight. */
  font-weight: 500;
  font-size: 40px;      /* .AppFooter-contentText, landscape */
  line-height: 1.1;     /* their html line-height */
  color: #FFFFFF;
}

/* ── THE CONTACT ROW — geometry read off pellmell.fr's own button ───────────
     .AppButton       display:inline-flex; align-items:center;
                      gap: calc(var(--spacing,.1rem)*6) = 0.6rem = 6px;
                      font-weight: 650   <- DECLARED, NEVER RENDERED. See below.
     .AppButton-title font-size: var(--text-18, 1.8rem) = 18px
     .AppButton .pl-square  width/height 8px; border-radius: var(--radius-1, 1px)

   Confirmed against the user's DevTools read at 1440x768: title 18px, box 131.523 x 19.797.

   THE 650 IS NOT A WEIGHT WE CAN COPY. `season` ships as ONE static file —
   `@font-face { font-family: season; src: /season-500.woff2; font-weight: 500 }` — so
   every weight the site declares resolves to that single Medium face. The user's
   Rendered Fonts panel confirms it end to end: the tagline computes 400 and the contact
   link computes 650, and BOTH paint `SeasonSansTRIAL-Medium`. The declared numbers and
   the painted face disagree, and the face is what you see.
   Inter IS variable 100-900, so writing 650 here would give us a genuinely heavier link
   than pellmell.fr paints. Both elements therefore take MEDIUM 500 — matching the
   RENDERED weight, not the declared number (user, 2026-08-24).

   TYPEFACE AND COLOUR ARE OURS, NOT THEIRS. Inter (variable, 100-900, so 650 is a
   real instance and not a synthesised bold) and #FF3567. Their face is "season" and
   their square is rgb(255,36,58) — neither is imported. These are sizes only.

   The arrow is OURS — their button renders an empty slot where ours carries one — so
   it keeps the ratio this build already used rather than an invented one: it was
   28 against 32px text (0.875), which at 18px is 16. */
.pi-footer__contact {
  margin-top: 20px;
  display: inline-flex;
  align-items: center;
  gap: 6px;             /* .AppButton gap */
  font-family: 'Bricolage Grotesque', 'Lexend', sans-serif;
  font-weight: 500;     /* Medium — the face .AppButton actually RENDERS, not its declared 650 */
  font-size: 18px;      /* .AppButton-title, landscape */
  /* 1.1 is their root line-height, and it is what makes their measured title box
     19.797 tall: 18 x 1.1 = 19.8. With line-height 1 ours measured 18.000. */
  line-height: 1.1;
  color: #FFFFFF;
  text-decoration: none;
}

/* ── THE SQUARE, AND ITS HOVER ROLL ────────────────────────────────────────
   8px at radius 1, their .pl-square exactly. Radius dropped 3 -> 1 with the size:
   3 on an 8px box is 37.5% and stops reading square.

   FLAT, NOT A 3D CUBE (user, 2026-08-24: "can we just use a 2D rotation like theirs").
   This was a real two-faced cube — preserve-3d, a 64px perspective host, two pseudo
   faces half a cube apart, and translateZ(-4px) to keep whichever face was forward at
   exactly z=0. All of it is gone, and the reason is that IT COULD NOT BE SEEN: both
   faces painted the same #FF3567, so a 90deg tumble swapped one pink square for an
   identical pink square. With no translation either, the only visible change was the
   1.1 swell — which is why the roll was reported as simply not happening.
   pellmell.fr's .pl-square is a single flat span. Rotation about its centre is likewise
   congruent for a square, so THEIR visible gesture is not the rotation at all — it is
   the x:"50%" (4px) carry and the swell, with the corners only showing mid-turn. That
   is now what this does, driven by js/pellmell-intro-footer.js.

   ONE ELEMENT, NOT TWO. With no perspective to host and no faces to hold, the inner
   .pi-footer__cube had nothing left to do, so .pi-footer__bullet and it collapse into
   this single .pi-footer__square — the same shape their markup uses. */
.pi-footer__square {
  flex: none;
  display: block;
  width: 8px;
  height: 8px;
  background: #FF3567;
  border-radius: 1px;
  /* No transform, transform-origin or transition: GSAP owns all three, and the default
     50% 50% origin is the one AppButton relies on. Declaring an origin here is what
     turned the Recent-work roll into a hinge. */
}

/* THE LABEL NUDGE — 8px, and the arrow travels with it.
   8px is their measured value: .AppFooter-navItem:hover span { transform: translate(.8rem) }
   with 1rem = 10px at desktop. The ARROW moves too, which their nav items do not have to
   solve: their span is the last thing in the link, ours has an arrow 6px after the label,
   so nudging the label alone would close that gap to -2px and overlap it. Moving the pair
   keeps the 6px and still reads as the label pulling away from the square. */
.pi-footer__contact-label,
.pi-footer__arrow {
  /* No transition and no :hover transform: the pair's x is written every frame by
     js/pellmell-intro-footer.js, read straight off the cube's current angle so the nudge
     cannot drift out of step with the roll. The 8px below is retained only as the
     documented travel; the JS holds the live value. */
}

.pi-footer__arrow {
  flex-shrink: 0;
  /* 16 -> 10 WITH THE GLYPH SWAP (2026-08-24), and the two numbers are not comparable.
     The old arrow was a 24-box STROKE drawing whose ink filled ~69% x 57% of its element,
     so a 16px box painted about 11.1 x 9.1 of actual arrow. The 45-degree glyph is square
     and, with its viewBox cropped to the ink, fills its element completely — so 10px here
     paints 10 x 10 of ink, sitting between the old arrow's width and height. Keeping 16
     would have rendered it 44% larger than what it replaced. */
  width: 10px;
  height: 10px;
}

/* ── LINKEDIN — A TWO-COPY ROLL, NOT A NUDGE AND NOT A COLOUR CHANGE ────────
   Read off pellmell.fr's own social links, which do something different from their nav:

     .AppFooter-socialsItemLink            { clip-path: inset(0) }
     .AppFooter-socialsItemLink span       { display:block;
                                             transition: transform .4s var(--ease-power3-out) }
     .AppFooter-socialsItemLink::after     { content: attr(data-title);
                                             color: var(--color-grey-3, #959697);
                                             position:absolute; left:0; top:0;
                                             transform: translateY(100%);
                                             transition: transform .4s var(--ease-power3-out) }
     :hover span                           { transform: translateY(-100%) }
     :hover::after                         { transform: translateY(0) }
     hover pair gated by @media (hover:hover) and (pointer:fine)

   THE GREY IS NOT THE LINK CHANGING COLOUR. Forcing :hover on their link leaves its own
   colour white — the grey is a SECOND COPY of the label, drawn by ::after from
   data-title, that rides up from below as the white one leaves. Both are masked by the
   clip-path, so the swap happens inside the link's own box. #959697 is their
   --color-grey-3.

   .4s HERE, NOT THE .6s THE CONTACT ROW USES. Their socials and their nav items carry
   different durations; both are theirs, so both are kept rather than averaged.

   THE 8px NUDGE THIS ELEMENT HAD IS GONE. It was applied when "the same treatment as the
   contact link" was the instruction; their socials roll instead, and the two do not
   combine — a rolling label that also slides sideways reads as two effects. */
.pi-footer__social {
  position: relative;
  display: inline-block;
  /* 28 -> 96 (user, 2026-08-24). Their .AppFooter-socials landscape margin-top is
     calc(var(--spacing,.1rem) * 60) = 6rem, and --spacing IS defined (:root, .1rem).
     NOTE the two readings of that 6rem disagree: at the site's own root it is 60px
     (landscape sets --size:1440, so font-size: calc(1000/1440*1vw) = 10px at a 1440
     viewport), while 96px is what 6rem gives at a 16px root. 96 is the user's measured
     DevTools value and is what ships. */
  margin-top: 96px;
  /* masks both copies to the link's own box, so the swap happens inside it */
  clip-path: inset(0);
  font-family: 'Bricolage Grotesque', 'Lexend', sans-serif;
  font-weight: 600;
  font-size: 13px;      /* .AppFooter-socials, landscape (was 14) */
  line-height: 1;
  letter-spacing: 0.08em;
  color: #FFFFFF;
  text-decoration: none;
}
.pi-footer__social-label {
  display: block;
  transition: transform var(--pi-foot-roll-dur) var(--pi-foot-ease);
}
/* the incoming grey copy, parked one line below */
.pi-footer__social::after {
  content: attr(data-title);
  position: absolute;
  left: 0;
  top: 0;
  color: #959697;                    /* their --color-grey-3 */
  transform: translateY(100%);
  transition: transform var(--pi-foot-roll-dur) var(--pi-foot-ease);
}
/* gated exactly as theirs is — a touch device has no hover to roll */
@media (hover: hover) and (pointer: fine) {
  .pi-footer__social:hover .pi-footer__social-label { transform: translateY(-100%); }
  .pi-footer__social:hover::after { transform: translateY(0); }
}

/* ── HOVER TIMING — one duration and one curve, spent by every moving part ───
   Both are READ OFF pellmell.fr, and off the one hover they actually publish a
   transition for: .AppFooter-navItem span { transition: transform .6s var(--ease-power3-out) },
   with --ease-power3-out: cubic-bezier(.165,.84,.44,1).

   NOT the site-wide --default-transition-duration (.15s) / cubic-bezier(.4,0,.2,1):
   those are their global defaults and are not measured on this element — the roll
   itself carries no transition at all, being driven inline from JS.

   ONE token each, spent by the square, the label, the arrow and LINKEDIN, so the
   square and the copy cannot drift apart and everything settles on the same frame. */
:root {
  --pi-foot-roll-dur: 0.4s;   /* .AppFooter-socialsItemLink — the LINKEDIN roll */
  --pi-foot-ease: cubic-bezier(0.165, 0.84, 0.44, 1);
}

/* The hover is an affordance, not a reveal — under reduced motion it simply does not
   move. Nothing is hidden behind it, so there is no state to swap instantly instead. */
@media (prefers-reduced-motion: reduce) {
  /* The cube, its label and its arrow are no longer listed here: they have no
     transition and no :hover transform left to neutralise. Their whole gesture is
     js/pellmell-intro-footer.js, which returns early on this same query, so under
     reduced motion nothing arms it and nothing moves — the documented intent, now
     enforced at the source rather than by cancelling rules that no longer exist. */
  .pi-footer__social-label { transition: none; }
  .pi-footer__social::after { transition: none; }
  .pi-footer__social:hover .pi-footer__social-label { transform: none; }
  .pi-footer__social:hover::after { transform: translateY(100%); }   /* stays parked */
}
