/* 7-Eleven project screen — INTRO-1 (index.html) ONLY.

   ISOLATED COPY, same contract as css/pellmell-intro-ef.css: the portfolio's
   the portfolio index-portfolio-archived.html keeps loading its own 7-Eleven rules from css/styles.css and is NOT
   touched by this file; v2/v3 never load it. Portfolio ships do not move INTRO-1
   and INTRO-1 ships do not move the portfolio. If the portfolio 7-Eleven styles
   change, sync by hand.

   WHAT IS A VERBATIM PORT AND WHAT IS NEW
   Ported unchanged from styles.css (the working screen is the source of truth):
   the six .se-cat cells, the .se-ico 42px frame, all six per-icon heights (the
   LOCKED v=289 baseline), the swap/hover crossfade, and the whole Hot-foods
   bubble build. Every one of those numbers is byte-identical to the portfolio.
   NEW here (user 2026-07-30, "Option B"): the hero is no longer one flat raster.
   The portfolio's 7-11-hero.svg embeds an 800x397 PNG with the promo copy and the
   Find-a-Store button BAKED IN, which would be visibly soft blown up to 1205px.
   So the photo-only re-export carries the image and the copy is rebuilt as live
   HTML in Roboto over it (.se-promo below).
   ========================================================================= */

/* ROBOTO IS SELF-HOSTED, NOT LINKED FROM A FONT CDN. INTRO-1's stated convention
   (the portfolio index-portfolio-archived.html, and the Inter/Satoshi faces in pellmell-intro.css) is
   @font-face off assets/fonts — "no external font host, for the same CDN-block
   robustness as the vendored libs". A Google Fonts <link> would have broken that,
   so the latin subset is vendored to assets/fonts/roboto-latin.woff2 (37.5 KB).
   It is the VARIABLE face, so one file serves 400/500/700 and no weight is
   synthesised. Declared here rather than in pellmell-intro.css because that file
   is the shared base v2/v3 also load — this keeps the port isolated. */
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 100 900;              /* variable weight axis */
  font-display: swap;
  src: url('../assets/fonts/roboto-latin.woff2') format('woff2');
}

/* =========================================================================
   FOOTPRINT — mirrors the INTRO-1 EF standard exactly (pellmell-intro-ef.css).
   The screen keeps an 800-wide INTERNAL coordinate space so every ported number
   above stays byte-identical, and ONE transform renders it at the INTRO-1 card
   size. Internal 800x438 -> scale 660/438 = 1.50685 -> 1205.48x660 painted,
   which is the SAME painted box as the EF stack (1205.48x660) and within 1.51px
   of #heroCard (1203.97). That is what makes the three project screens agree.

   Internal height 438 = hero 352 + strip 86. The strip is the ported 86; the
   hero is 352, not the portfolio's 341, because the total has to hit 438 to land
   on the EF scale. The hero is now a cover-fitted photo box rather than a fixed
   raster, so its height is free and absorbs the 11px.

   CONTAINER WIDTH (user asked for the read): RULES locks the INTRO-1 card at
   1276.5 at 1440, and the Figma reference 1894:1610 is 1093 wide. They do not
   conflict — they measure different boxes. 1276.5 is the CONTENT/row width; the
   PAINTED card is ~1204 (#heroCard 1203.97, EF stack 1205.48). Figma's 1093x600
   is aspect 1.8217 against the painted card's 1.8243 — 0.14% apart — i.e. the
   same card drawn x1.1. So this builds to the painted card, and the reference is
   read as proportion only.
   ========================================================================= */
.se-project-screen {
  position: relative;
  z-index: 200;               /* above the fixed .hero (z-index 100), as EF */
  min-height: 100vh;
  background: #ffffff;        /* opaque: occludes the pinned hero scrolling under */
  display: flex;
  align-items: flex-start;
  justify-content: center;
}
/* Fallback (reduced-motion / no-JS): #scrollTrack collapses to 0, so push this
   screen below the first viewport to preserve Matey-first ordering. EF does the
   same; this one sits a further viewport down so EF still precedes it. */
html:not(.js-loading) .se-project-screen {
  margin-top: 100vh;
}

.project-row--live[data-theme="seven-eleven"] {
  width: 1276px;
  height: 660px;
  border-radius: 0;
  overflow: visible;
  display: flex;
  align-items: stretch;
}
.project-row[data-theme="seven-eleven"] .visual-panel {
  width: 1276px;
  height: 660px;
  background: transparent;
  position: relative;
  overflow: hidden;
}
/* left = (1276 − 800×1.50685) / 2 = 35.26 — the SAME centring offset the EF stack
   uses, so the two screens paint on identical left/right margins. */
.project-row[data-theme="seven-eleven"] .se-screen {
  position: absolute;
  left: 35.26px;
  bottom: 0;
  transform: scale(1.50685);   /* 660 / 438 — height-fit, no clip */
  transform-origin: bottom left;
}

/* =========================================================================
   THE SCREEN — internal 800x438. Ported from styles.css .se-screen except for
   the height (see footprint note) and the font stack.
   TYPEFACE: Roboto (user 2026-07-30). The portfolio has two families in play —
   .se-screen sets Lexend and .se-label overrides with Inter 600 — and Lexend is
   INERT there, because the only live text in the portfolio screen is the six
   labels (its hero is raster, its icons are SVG). Here the promo copy is live
   too, so Roboto genuinely covers eyebrow / offer / paragraph / CTA / labels.
   ========================================================================= */
.se-screen {
  width: 800px;
  height: 438px;
  flex-shrink: 0;
  background: #FFFFFF;
  /* 16 -> 35.04 (2026-07-30). The portfolio's 16px is right for an 800-wide card
     floating in a panel; here the card renders at 1205 beside the EF screen and the
     Matey #heroCard, both of which paint a 52.8px corner. 35.04 x 1.50685 = 52.80
     rendered, the same derivation pellmell-intro-ef.css uses for the EF hero at rest
     (commit 9baf6b7). If .se-screen's scale changes, this radius must change with it.

     35.04 -> 6.6364 (user, 2026-08-24): the project containers now match the FOOTER's
     10px corner, not the card family's 52.8. AUTHORED 6.6364 because this box renders
     at 1.50685x — 6.6364 x 1.50685 = 10.0000 PAINTED. Writing 10 here would paint 15.07.
     The 52.8 family match documented above is deliberately broken; see the note in
     Docs/RULES.md. */
  border-radius: 6.6364px;
  overflow: hidden;
  /* NO BOX-SHADOW (user, 2026-08-05). Was `0 12px 40px rgba(0,0,0,0.08)`. Diagnosed as
     the faint tint reported outside all four rounded corners: at 8% black with a 40px
     blur against a 12px offset, the blur exceeds the offset in every direction, so the
     shadow showed in the corner notch where no card covers it. Removed rather than
     retuned — the corners are meant to be clean. This is the LIVE row only; the wave
     row's own .wv-screen rule is separate and untouched. */
  font-family: 'Roboto', sans-serif;
}

/* ---- HERO: photo layer + live copy ---------------------------------------
   THE PHOTO IS assets/seven-eleven/7-11-hero-1093x600.png (user's updated visual ref,
   2026-08-24). It replaced 7-11-hero-med.png (800x341).

   WHY IT WAS REPLACED — THE CORNER WAS IN THE ASSET, NOT IN CSS. The old file is RGBA
   with a rounded corner PAINTED INTO IT: its alpha channel steps in ~12 image px at the
   top-left, i.e. ~18px painted through this screen's 1.50685 scale. Every border-radius
   change on .se-screen left it untouched, because the box was never what was drawing
   that corner. Measured off the file's alpha, not inferred. The new ref bakes ~7px.

   IT IS CROPPED, DELIBERATELY (user: "leave it as is"). The new ref is 1093x600, aspect
   1.822, against this band's 800x352 = 2.273. At .se-hero-photo's width:104% it paints
   1253.7x688.2 and sits 78.9px above the box top, so overflow:hidden takes ~79px off the
   top and ~79px off the bottom. A side effect is the fix: the baked corner is cropped
   away entirely, leaving a square top-left that .se-screen's 10px radius then rounds.
   Do NOT "restore" the full frame by re-proportioning the band — the crop is the
   accepted trade, and the corner depends on the top being cut. */
.se-hero {
  position: relative;
  width: 800px;
  height: 352px;
  /* YELLOW GROUND UNDER THE PHOTO (user, 2026-08-24) — the corner fix that does not
     depend on where the image happens to crop. Anything transparent in the asset now
     resolves to yellow instead of .se-screen's #FFFFFF, so the baked corner arc, the
     soft top rows and the soft last row all blend away and the ONLY corner drawn is
     .se-screen's own 10px clip.
     #F7CB45 IS THE ASSET'S OWN YELLOW, sampled not assumed: every pixel down the left
     edge of the visible band reads exactly (247,203,69), and that is the same value
     .se-cats already fills with — so the hero ground, the strip and the two corner
     patches are now one colour rather than three that happen to agree. */
  background: #F7CB45;
  overflow: hidden;
}
/* ── 2026-07-31: ONE CHANGE FIXES BOTH REPORTED DEFECTS, AND THEY HAD ONE CAUSE ──
   Was: width/height 100% + object-fit:cover + object-position:center.

   (1) THE GAP ABOVE THE NAV STRIP WAS NEVER A LAYOUT GAP. The boxes are flush —
   .se-hero 352 + .se-cats 86 = 438 exactly, and the strip does start at internal
   y 352. NOTE (2026-08-24): everything from here to the end of this block describes
   the RETIRED 7-11-hero-med.png. It is kept because the reasoning — that a soft last
   row in the asset reads as a seam, not a layout gap — is what to check first if a
   seam ever reappears. The current asset is cropped top and bottom, so its last row
   never reaches the box edge and this particular fault cannot recur the same way.
   The gap WAS IN THE OLD ASSET: 7-11-hero-med.png is RGBA and its LAST ROW
   (y=340) is 87% transparent (mean alpha 32 of 255; rows 0-2 are also slightly
   soft at 246-250). Figma confirms it independently — node 1891:1575 is a 341-tall
   frame whose base photo vector 1892:1589 is only 340.34 tall, so the export
   carries a 0.66px empty remainder. `cover` mapped that row onto the very bottom
   edge of the box, where .se-screen's white background showed through it and read
   as a pale seam against the yellow. Measured at paint: a ramp from solid yellow
   (247,205,79) at internal y 350.40 up to near-white (254,249,231) at 351.40
   before the strip's #F7CB45 begins.

   (2) THE FEEDBACK TAB WAS CLIPPED BY THE SAME `cover`. The tab plate (green
   53,113,83, distinct from the brand band's 49,114,82) occupies source x 775-799.
   `cover` renders 825.81 wide into an 800 box and CENTRE-anchors it, so the
   visible window was source 12.50..787.50 — leaving exactly 12.50px of the tab on
   screen. Partially visible, which is the worst of both.

   THE FIX — resolved as "not visible at all" (the user's second option). That is
   also what the larger-container reference 1894:1610 shows: no tab, subject
   tighter. Chosen over "fully visible" because showing the whole 800x341 frame
   uncropped would force the screen's internal aspect to 800:427, which would
   break the painted box away from EF's 1205.48x660 — a worse trade than losing a
   25px UI artifact.

   width 104% + left-anchored + vertically centred, natural aspect (no object-fit):
     rendered 832 x 354.64
     visible source window 0..769.2  -> tab starts at 775, clears by 5.8px
     vertical overflow 2.64 -> 1.32 clipped top AND bottom; the transparent row
     needs 1.04, so it is fully outside the box, and the soft top rows go too.
   THE ASSET IS UNTOUCHED — this is a framing change, not a re-export. If the photo
   is ever re-exported without the transparent row, 104% can drop back toward 103%
   (tab clearance is what sets the floor: 103% still leaves the tab 1.7px inside). */
.se-hero-photo {
  display: block;
  position: absolute;
  left: 0;
  width: 104%;
  height: auto;

  /* BOTTOM-ANCHORED BY EXACTLY THE CLIP (user, 2026-08-24 — "a band of plain yellow
     where the photo used to reach").
     The photo box was CENTRED (top:50% + translateY(-50%)), which put its bottom at
     404.36 in hero coords. The clip then took 82 off that, ending the image at 322.36
     while the hero window runs to 352 — so ~29.85px of bare .se-hero yellow sat between
     the photo's last row and the live .se-cats strip. The earlier note below assumed
     that band would read as continuous with the strip because both are #F7CB45; it does
     not, because the photo carries diagonal banding that simply stops short.
     MEASURED OFF THE ASSET, not guessed: sampling every third pixel per row, the photo's
     last content row is image 492 (32.1% asset-yellow, hero y 322.15), row 493 is the
     baked strip's top border (0% yellow) and 494+ are its flat ground (99.2%).
     THE FIX IS TO STOP CENTRING. Anchoring the box `bottom: -clip` pushes it down by
     precisely what the clip removes, so the clipped edge lands ON the hero's bottom edge:
     box bottom 352 + 82 = 434, visible bottom 434 - 82 = 352 exactly. The window then
     shows image rows 29.85..492.3 — the photo's full content, ending on its last row,
     with the baked border and strip entirely below the hero.
     NOTHING SOFT IS REVEALED at the new top. The asset is fully opaque from row 1 to 598
     (only rows 0 and 599 are anti-aliased at ~97.5%), and the new top edge sits at row
     29.85, well inside that.
     ONE VARIABLE DRIVES BOTH so they can never drift apart — retune --se-hero-clip alone
     and the anchor follows. */
  --se-hero-clip: 82px;
  bottom: calc(-1 * var(--se-hero-clip));
  /* CLIP THE ASSET'S OWN NAV STRIP OFF (user, 2026-08-24 — two nav strips on screen).
     7-11-hero-1093x600.png is the FULL comp, so it bakes the category strip as well as
     the copy and the corner. The photo box is 456.72 authored tall and only the middle
     352 is visible, but the crop is centred, so the baked strip's TOP still landed inside
     the window: it begins at image row 494 = authored 323.67, and the hero shows to 352 —
     ~28px of it, which read as a second row of half-icons above the live .se-cats.
     Boundary measured off the asset, not guessed: rows up to 492 are ~32% yellow (photo),
     row 493 is 0% (the strip's own top border) and 494+ jump to 98.7% (its flat ground).
     404.36 - 322.36 = 82 from the photo's bottom hides the border and everything under it
     while keeping every row of actual photo.
     WHAT SHOWS INSTEAD IS .se-hero's #F7CB45, which is the same value the baked strip's
     ground was (98.7% of those rows) and the same .se-cats fills with — so the hero's
     lower band, the gap and the live strip are one continuous yellow.

     NOT CAUSED BY THE PROMO FILL. That fill spans authored y 37..212 and never reached
     322; this arrived with the asset swap and was simply not noticed until the copy above
     it was cleaned up. */
  clip-path: inset(0 0 var(--se-hero-clip) 0);
}

/* ---- promo copy (NEW — rebuilt live, was baked into the raster) -----------
   POSITIONS ARE BY EYE against Figma 1894:1610, which the brief set as
   directional for layout and content, not a geometry spec — and which could not
   be extracted from anyway (frame 1892:1590 is flattened, no text children).
   Strings are transcribed from the portfolio's own hero raster, which is the
   content source of truth.

   Both sources were measured and they disagree, so these are chosen, not copied.
   In internal 800-space, built raster vs reference (reference / 1.36625):
       left inset      built 22      reference 36.6     CHOSE 36
       eyebrow cap h   built  9      reference  8.8     CHOSE 12px type
       offer band h    built 42      reference 36.6     CHOSE 38px type
       CTA box         built 81x24   reference 100x28.6 CHOSE 100x28
   The reference wins on the left inset and the CTA (its more generous margin and
   larger pill are the visible intent of the re-comp); the built raster wins on
   the offer, which reads better big and is the existing brand pose. Everything
   lands between the two, none of it is transcribed as spec. */
.se-promo {
  position: absolute;
  left: 36px;
  top: 93px;
  width: 352px;
  color: #000000;
  /* above its own yellow ground (::before sits at z-index -1) */
  z-index: 1;
}
/* ── YELLOW FRAME BEHIND THE LIVE COPY (user, 2026-08-24) ───────────────────
   THE ASSET NOW HAS THE COPY BAKED IN. 7-11-hero-med.png was a PHOTO-ONLY export;
   7-11-hero-1093x600.png is the full comp, so the headline, the paragraph and a white
   "Find a Store" pill are all painted into the image. Rendering the live block on top of
   it doubled every line — two sets of type a few px apart, which reads as blur, not as
   text. Verified by hiding .se-promo and re-rendering: the copy and the pill are still
   there, in the picture.

   The fix is the one used in the Figma source: a frame with a yellow fill behind the
   text. ::before rather than a background on .se-promo itself, because the baked copy
   does NOT sit inside .se-promo's box — the new asset is scaled and cropped differently
   from the original comp, so its headline starts ~30px ABOVE the live block's top. The
   negative insets cover that overhang without moving a single line of live type.

   IT IS INVISIBLE WHERE IT LANDS. The asset's flat yellow field reaches authored x~591
   and y~322; this fill spans x 16..408 / y 49..bottom+12, entirely inside it. #F7CB45 is
   that field's own value, the same one .se-hero, .se-cats and the corner patches carry —
   so the frame reads as more of the yellow, and only the baked ink under it disappears. */
.se-promo::before {
  content: "";
  position: absolute;

  /* ── THE FILL THAT COVERS EVERYTHING BAKED INTO THE HERO ASSET ──────────────
     7-11-hero-1093x600.png is the FULL comp, so it bakes the promo block as well as
     the photo. This rectangle is the ONLY thing hiding it, and it is now sized from a
     measurement of every baked element rather than from whichever one was last
     reported (user, 2026-08-24: "fix the whole class once").

     WHY IT BROKE, AND THE ACTUAL LESSON. It used to be written as right/bottom, so its
     bottom edge was `.se-promo`'s auto content height minus 41px. TWO moving parts fed
     it: the promo's own text reflow, and the asset's position. When .se-hero-photo was
     re-anchored from centred to bottom:-clip earlier the same day, every baked element
     moved DOWN 29.85px while the fill — pinned to a DOM box that had not moved — stayed
     put, and the baked "Find a Store" pill slid straight out from under it. That is why
     this has now surfaced three times as three different single elements: the corner,
     the nav strip, the eyebrow, the pill. They were never separate faults.
     SO THE BOX IS NOW ABSOLUTE: top + height + left + width, in authored px. It no
     longer depends on the promo's content height at all, and the only thing that can
     invalidate it is moving the ASSET — which is exactly the one input it should track.
     IF .se-hero-photo's anchoring CHANGES AGAIN, RE-MEASURE THIS. The mapping is
     authored_y = imageRow * 0.7612077 + boxTop, boxTop = (352 + clip) - 456.7246.

     EVERY BAKED ELEMENT, measured off the PNG at the current anchoring, authored hero px:
         eyebrow "ONLY WITH REWARDS(R)"   y  74.71 ..  83.08
         offer   "2/$2 Ring Donuts"       y  89.93 .. 127.23
         copy line 1                      y 137.89 .. 148.55
         copy lines 2-4                   y 151.59 .. 179.00
         copy line 5                      y 181.28 .. 191.94
         pill    "Find a Store" (WHITE)   y 211.73 .. 236.85   <- the ghost copy
         corner arc / soft top rows       above y 0, and handled by .se-hero's #F7CB45
         category nav strip               below the hero, handled by the photo's clip
     Leftmost ink is x 38.06, rightmost 351.1. This box spans y 37..243, x 16..362, so
     every band above sits inside it with margin — the pill clears by 6.15px at the
     bottom, where the old 212.3 edge cut it 24.55px short.

     THE RIGHT EDGE STILL CLEARS THE ORANGE STRIPE. 362 against baked ink at 351.1 and
     the stripe's diagonal at ~374 for these rows. Note the vertical shift only WIDENED
     that corridor: the stripe leans left as it descends, so at any given y it now sits
     where a row 29.85px higher used to be, which is further right. */
  left: -20px;      /* hero x 16  */
  width: 346px;     /* hero x 362 */
  top: -56px;       /* hero y 37  */
  height: 206px;    /* hero y 243 */
  background: #F7CB45;
  z-index: -1;
}
/* WEIGHTS (user asked me to pick and report):
   eyebrow 700 — it is small uppercase over a saturated yellow and needs the mass
   offer   700 — Roboto Bold is the display weight; 900 crushes at 38px
   copy    400 — body regular
   CTA     700 — matches the eyebrow so the block reads as one voice
   labels  500 — see .se-label
   Import is Roboto:wght@400;500;700 — three weights, no 600, so nothing is
   synthesised by the browser. */
.se-eyebrow {
  margin: 0;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.02em;
}
.se-offer {
  margin: 2.7px 0 0 0;
  font-size: 38px;
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.01em;
}
.se-copy {
  margin: 4.7px 0 0 0;
  width: 340px;
  font-size: 11.5px;
  font-weight: 400;
  line-height: 1.19;
}
.se-cta {
  margin-top: 18.4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 100px;
  height: 28px;
  padding: 0 18px;
  border-radius: 14px;
  background: #FFFFFF;
  color: #000000;
  font-size: 12px;
  font-weight: 700;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
}

/* ---- shop-by-category strip — VERBATIM PORT ------------------------------
   Everything from here down is byte-identical to css/styles.css apart from the
   .se-label family/weight. In particular the .se-ico 42px frame and the six
   per-icon heights are the LOCKED v=289 baseline and are NOT changed: the single
   container transform renders them 1.50685x larger without touching a literal.
   That is why this build needed no override. It does mean the icons paint ~9.3%
   larger than Figma 1894:1610 implies — the reference's icons are 1.250x the
   locked visible targets while its container is 1.3662x the source width, so
   normalised they are 0.9149x. Matching that would mean multiplying the whole
   locked table by 0.9149 (frame 42 -> 38.43), i.e. making them SMALLER, and that
   is the override this build deliberately does not take. */
.se-cats {
  display: flex;
  align-items: stretch;
  height: 86px;
  /* STATIC STRIP FILL — #F7CB45, matching the wave row (user, 2026-08-05). Was
     #F3F3F3. This is the RESTING state only; the band colours on this row are
     unchanged (red #eb292e / yellow #f3d449 / green #34c28c). */
  background: #F7CB45;
}
/* ── THE BOTTOM CORNERS ARE THE CLIP'S JOB, AND NOTHING ELSE'S ───────────────
   NO ::before / ::after HERE ANY MORE. Two of them lived here and both were wrong:

   1. Originally a pair of 0.5px #F9D874 quarter-arcs restoring the stroke the rounded
      clip ate from .se-cat's square-cornered border. Right at a 52.8px corner; at 10px
      the arc is a 0.75px-painted fragment that only reads as fuzz.
   2. Then a pair of solid #F7CB45 squares at z-index 3, which fixed the fuzz AT REST and
      broke the SWEEP. The strip is only #F7CB45 when idle: the sweep repaints it
      green #34c28c, red #eb292e and orange #ff9900 in turn, and those two patches sat
      ABOVE the canvas (z 0) and the cells (z 2) holding yellow through all of it —
      a 10px square stuck in each bottom corner while the colour ran past
      (user, 2026-08-24: "a small square appears at the bottom right corner as the
      animation runs").

   THE CORNER NEEDS NOTHING. .se-screen carries border-radius 6.6364 (10px painted) and
   overflow: hidden, so it clips whatever the strip currently IS — idle yellow, a sweep
   colour, the field, the cells — and the corner follows the colour instead of fighting
   it. The lesson worth keeping: this strip is REPAINTED, so anything placed over it must
   be transparent or animated with it. A literal colour here is a bug waiting for the
   next cycle. */

.se-cat {
  box-sizing: border-box;
  width: 133.333px;
  /* STRIP STROKE — #F9D874 (user, 2026-08-05). Was #D5D5D5, NOT white: the brief
     said "white -> #F9D874", but white was the wave row's interim value; this row
     has always run #D5D5D5. The destination is the same either way. */
  border: 0.5px solid #F9D874;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  text-decoration: none;
}
/* SCALE 1.12 and its 0.22s transform transition are gone with the per-icon motion layer.
   Nothing moves .se-ico now — :hover included. The strip's only motion is the sweep and
   the field, and a hover pop competed with both. .se-ico stays `relative` because the
   green artwork is absolutely positioned inside it. */
.se-ico {
  height: 42px;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
.se-ico-img {
  width: auto;
  display: block;
}
/* RE-DERIVED 2026-08-05 FOR THE NEW ICON SET — OVERRIDE ON THE v=289 LOCK, granted
   explicitly by the user. INTRO-1's live 7-Eleven row only; the portfolio row in
   the portfolio index-portfolio-archived.html and the seven-eleven-wave row still run the v=289 table, untouched.
   Previous (v=289, Figma node 1158:2501 instance sizes), kept for the revert:
       hot-foods 28.45  fresh-chilled 29.51  snack-food 38.02
       coffee    39.82  ice-cream     39.94  cold-pressed-juice 39.95

   WHY THEY HAD TO MOVE AT ALL — the height applies to the VIEWBOX, not the art.
   The v=289 numbers were valid because each old viewBox was reframed tight to its
   own stroke, so viewBox height and visible height were nearly the same number.
   That is not true of the new set: icon-snack-outline is a 72x72 padded square
   holding art 40.474 x 54.103, and icon-ice-cream-outline is 90x90 holding
   35.421 x 75.407. Carrying the old heights over would have shrunk the rendered
   art 20.8% and 13.0% respectively, purely by feeding padding into the budget.

   THE TARGET IS "NO VISIBLE CHANGE": each height is solved so the RENDERED ART
   keeps the exact size it had at v=289 —
       newH = oldH * (oldVisH/oldVbH) / (newVisH/newVbH)
   Measured by alpha-scanning a 1400px raster of every file in headless Chrome and
   back-projecting to user units, so the numbers are true visible extents including
   strokes, not getBBox() geometry. The comment after each rule is the held art
   height in px — those six are UNCHANGED from v=289 by construction, which is the
   check that this override moved the container and not the artwork.

   Two widths do move, and cannot be held, because the drawing itself changed:
   fresh-chilled +1.1% (55.61 -> 56.21) and ice-cream +1.5% (17.80 -> 18.06). The
   other four hold to within 0.1%. fresh-chilled is a genuinely different drawing
   from the sub icon it replaces — see TODO, logged, not compensated for here. */
.se-cat--hot-foods .se-ico-img          { height: 26.89px; }   /* art 26.88 — held */
.se-cat--fresh-chilled .se-ico-img      { height: 28.01px; }   /* art 27.10 — held */
.se-cat--snack-food .se-ico-img         { height: 48.28px; }   /* art 36.06 — held */
.se-cat--coffee .se-ico-img             { height: 38.23px; }   /* art 38.23 — held */
.se-cat--ice-cream .se-ico-img          { height: 46.13px; }   /* art 38.46 — held */
.se-cat--cold-pressed-juice .se-ico-img { height: 37.55px; }   /* art 37.55 — held */

/* ── AND THE SAME PADDING SHIFTS THE ART OFF-CENTRE, WHICH HEIGHT ALONE DOES NOT FIX ──
   Flex centres the <img> BOX. On the old set that was the same thing as centring the
   art, because each viewBox was reframed tight to its own stroke. On a padded canvas
   it is not: ice-cream's art occupies y 0.1271..0.9650 of its 90x90 viewBox, so its
   centre sits 4.61% of the box BELOW the box centre — 2.11px at the height above, on a
   42px frame. Held size, wrong place.
   Offset = (fy + fh/2 - 0.5) * height, from the same measured visible boxes:
       hot-foods -0.004  fresh-chilled -0.420  snack-food -0.166
       coffee    +0.000  ice-cream     +2.134  cold-pressed-juice +0.000
   Only the two padded canvases clear 0.05px and get a rule; the other four are zero to
   three decimals and are deliberately left alone rather than given no-op transforms.
   getBoundingClientRect() includes transforms, so measure() picks these up and the
   shader's art rects follow — the nudge is applied once, here, not twice. */
.se-cat--fresh-chilled .se-ico-img { transform: translateY(0.42px); }
.se-cat--snack-food .se-ico-img { transform: translateY(0.17px); }
.se-cat--ice-cream .se-ico-img  { transform: translateY(-2.13px); }

/* ── THE ATLAS HIDES THE DOM ICONS, AND ONLY ONCE IT IS READY ────────────────
   Set from JS (strip.classList.add('se-atlas-live')) after all 12 layers have
   loaded and the texture is uploaded — never from CSS unconditionally. The file
   bails before this on no-WebGL, on a shader/link failure, and on a missing
   atlas layer, and in every one of those cases the DOM outline must still be
   the icon. Visibility only: the <img> keeps its box, so CSS stays the single
   source of truth for icon position and size and measure() keeps reading it. */
.se-atlas-live .se-ico-img { opacity: 0; }

/* ── NO ICON SWAP HERE, DELIBERATELY ──────────────────────────────────────────
   The accent fill is revealed by the SAME curved edge that paints the band, at the
   same pixel boundary, so an icon mid-transition is part-outline and part-filled
   split along that curve. No CSS class can express that — a class toggles a whole
   element — so the fill variants are composited by the shader and the DOM carries
   the outline only. There is no `--green` / `--hover` / `--fill` layer left, and no
   `.is-green` rule: an interim binary swap lived here briefly during the rebuild and
   was removed, since a whole-icon flip is exactly the wrong behaviour.

   The `--swap` / `--active` / `:not(:hover)` machinery that used to sit here was the
   per-icon motion layer's outline→multicolour crossfade, driven by a class the
   auto-sweep toggled and gated on the strip not being hovered. All of it is gone
   (2026-06-08 lock released by explicit user override), together with the Hot-foods
   bubble build — the .se-yellow disc, the #B060F8 per-cell bubbles and the
   colour-fill burger. Hot foods now carries no rule the other five do not.

   :hover paints nothing here any more. It was the sole painter under the lock; the
   sweep is the sole painter now.

   The heights above stay: they size the OUTLINE, which is what this markup holds.
   The fill variants are framed differently — outline viewBox 0 0 54.22 50 against the
   variants' 0 0 30 28 — so the shader registers them at their own size onto the same
   measured visible centre, which is where that second column of numbers in the v=289
   comment (26.839, 27.104, 36.027, 38.201, 38.408, 38.403) is used. */

/* Portfolio has Inter 600 here. Roboto 500 (Medium) is the canonical UI weight
   and sits closest to Inter SemiBold's optical colour — Roboto runs narrower and
   denser, so 600/700 would read heavier than the portfolio does. Size and the
   rest of the box are untouched. */
.se-label {
  font-family: 'Roboto', sans-serif;
  font-weight: 500;
  font-size: 12px;
  line-height: 1.2;
  text-align: center;
  color: #000000;
  white-space: nowrap;
}
