/* ═══════════════════════════════════════════════════════════════════════════
   oro-qa.css — QA corrections from the 2026-08-04 responsive pass
   Loaded LAST, after oro-a11y.css. Pure overrides: creates no component and no
   new class. Deleting the <link> restores the previous rendering exactly.

   WHY THIS IS SEPARATE FROM oro-a11y.css
   That file is the other lane's and is being actively written. These four rules
   would sit naturally in it, but two sessions editing one stylesheet is how this
   repo lost oro-card2.json twice in one evening. The rules are idempotent — if
   that lane sets the same 44px targets, both files agree and the result is
   identical. Fold this into oro-a11y.css and delete it once the lanes merge.

   Every value below was MEASURED in the browser at 390x844 on a fresh load,
   not inferred. Recorded sizes are the pre-fix numbers.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── touch targets ──────────────────────────────────────────────────────────
   WCAG 2.5.8 (AA) asks 24x24 and all of these already cleared it, so none was
   a conformance failure. 44x44 is the platform guideline — Apple HIG and
   Material both — and these are the controls a user is most likely to hit in a
   hurry on a phone: a service-worker update prompt and a back-to-top.

   min-height/min-width rather than height, so a longer label still grows the
   button instead of overflowing it. */

/* PWA update + install prompts. Measured 84.7x39.4 and 67.5x39.4;
   oro-pwa.css sets min-height:40px, which lands just under. */
.oroa-btn,
.oroa-btn--ghost {
  min-height: 44px;
}

/* Toast dismiss. Measured 39.4x39.4 — square, so both axes need lifting.
   It is display:grid with the glyph centred, so the icon does not move. */
.oroa-toast__close {
  min-width: 44px;
  min-height: 44px;
}

/* ── NOT fixed here, and why ────────────────────────────────────────────────
   `.orom-top` (back-to-top) first measured 42x42 and looked like a third
   offender. It is not. Its LAYOUT box is 46x46 — already over the guideline —
   and the 42.3 reading is its hidden resting state: opacity 0 with a
   scale(.92) that only resolves to 1 when the control is revealed.
   getBoundingClientRect reports the transformed size, offsetWidth does not.

   A min-width/min-height floor on a 46px element would have been dead code
   dressed up as a fix, so there is no rule for it. The general lesson is worth
   more than the rule would have been: when auditing hit boxes, compare
   offsetWidth/offsetHeight against getBoundingClientRect, and take the
   measurement at rest — an element caught mid-animation or in a hidden state
   reports a size it never actually presents to a finger. */

/* ── the duplicated phone on the first mobile screen ─────────────────────────
   Reported repeatedly, and the earlier attempt here was wrong. Recording both,
   because the wrong one is the more instructive.

   WHAT IS ACTUALLY ON SCREEN
   .hero-phone-frame paints oro-hero-phone.webp (1520x3096, aspect 0.491): an
   EMPTY handset with a gold-lit screen. Stacked on top of it, .false-p paints
   oro-screen3.webp (1824x2600, aspect 0.702) — and that file is not screen
   content. It is a photograph of a COMPLETE HANDSET: bezel, side buttons, notch,
   status bar, home indicator, sitting on transparent padding.

   So the "second phone" was never a bleed or an overlap. It was a picture of a
   phone being drawn inside a picture of a phone. Both were behaving exactly as
   their CSS said; the CSS was pairing artwork that was never meant to pair.

   WHY THE FIRST FIX FAILED
   It set `inset: 0; width: auto`, which constrained the still to the frame's
   box. background-size:contain then fitted the 0.702-aspect artwork by width
   into a 0.493-aspect box: 332.7 x 474.3, centred. The nested handset stopped
   spilling past the frame and started sitting tidily INSIDE it instead — which
   looks more deliberate and is just as wrong. Verified on a real device by the
   site owner, who reported it still broken while a desktop browser looked fine.
   The desktop browser was reading a service worker's stale copy of this file.

   THE FIX
   Both artworks are the same handset at the same angle, and in both the handset
   is centred in its canvas (oro-screen3: 322px of padding left, 316 right, 114
   top, 114 bottom). So they can simply be made the same SIZE and allowed to
   coincide — one phone, wearing the app UI.

   Measured at 390x844 with the hero at rest:
     .hero-phone-frame box            332.7 x 675.2   (aspect 0.4927)
     oro-screen3 handset within file  1186 x 2372 px  (aspect 0.500)
     contain-fit at inset:0 renders that handset at   216.3 px wide
     wanted                                           332.7 px wide
     ratio                            332.7 / 216.3 = 1.538

   A uniform 1.538x needs the box grown by (1.538 - 1) / 2 = 26.9% per side.
   Percentage insets resolve top/bottom against the parent's height and
   left/right against its width, so a single -26.9% is uniform by construction —
   and it therefore holds at every viewport and through the hero's scroll-driven
   scale, which was measured shrinking the frame to 199.6 x 405.1 mid-scroll.
   Confirmed there: .false-p 307 x 623.1 against frame 199.6 x 405.1, ratio 1.538.

   Residual: the still's handset renders ~10px (1.5%) shorter than the frame's
   and ~5px high, because the two files differ slightly in aspect (0.500 vs
   0.491). At this scale that is inside the frame's own bezel and does not read.

   background-size stays `contain` from oro.css:4647 — it is already correct once
   the box is right, so it is not restated here. */
@media (max-width: 479px) {
  .hero-phone-screen.false-p {
    inset: -26.9%;
    width: auto;
    height: auto;
  }
}

/* ── the loading plate was a void on a phone ─────────────────────────────────
   Reported as "Loading screen Space". Captured at 390x844 on Slow 4G with a 4x
   CPU throttle, which is the only way to hold the plate on screen long enough
   to photograph it. What the visitor gets for the first seconds of the site:

     · the ORO wordmark, 343 x 141 — 17% of the screen height — and because it
       is drawn as a STROKE it is close to invisible for the first second. At
       the moment of capture it was a single 90px gold arc.
     · the meter — "ASSAYING", the rule, "001%" — pinned to the bottom edge,
       roughly 700px below the mark.
     · between them, nothing.

   Two independent causes, both worth naming.

   1 · THE METER IS PINNED TO A VIEWPORT THE READER CANNOT FULLY SEE.
       #oroload is position:fixed;inset:0, which resolves against the LAYOUT
       viewport — the tall measurement that assumes the address bar is hidden.
       .orl__meter is then absolutely positioned to `bottom`. While the address
       bar IS showing, the rail sits 60-110px below the visible area, so on a
       real phone the reader often sees the void WITHOUT the meter that explains
       it. Same class of bug as the drawer, same fix: 100dvh tracks the browser
       chrome, so the plate is exactly as tall as what is actually visible.

   2 · THE COMPOSITION IS A DESKTOP COMPOSITION.
       The bottom rail is a good idea at 1440x800, where the mark is 620 x 254 —
       a third of the height — and the rail reads as a measured foot to a full
       plate. The inline CSS comment records that intent. But the mark cannot
       grow on a phone: its aspect is 2.44:1, so even at 88vw it is only 141px
       tall, and a 141px mark with a rail 700px away is not a composition. It is
       two objects that have lost each other.

       The plate's own stylesheet already contains the right answer for this —
       the (max-height:620px) fallback stacks the mark and meter into one
       centred cluster so "the rail can never collide with the mark". A 390-wide
       phone needs that layout for the same reason a short viewport does, so it
       is applied by WIDTH as well as by height. Small and centred is the
       correct register for this brand anyway; the failure was the distance,
       not the scale.

   3 · AND THE FIRST SECOND WAS BLANK. .orl__fill sits at opacity 0 until the
       plate finishes, so before the stroke has drawn anything there is no
       wordmark at all — a black screen with a hairline. Resting the fill at a
       faint 0.09 puts the mark on screen from the first painted frame and lets
       the gold stroke draw over its own ghost. #oroload.is-done .orl__fill is
       (1,2,0) and still wins, so the resolve to full opacity is unchanged.

   All three are addressed from here rather than from the inline <style
   id="orl-css"> in index.html: that block is in <body>, so it wins ties against
   a <head> stylesheet on order alone. #oroload raises these to (1,1,0) and beats
   it on specificity instead, which is also why this can live in the QA lane. */
@media (max-width: 700px) {
  #oroload { height: 100dvh; }

  #oroload .orl__in {
    position: relative;
    width: min(560px, 84vw);
    gap: clamp(26px, 8vw, 40px);
  }

  #oroload .orl__meter {
    position: static;
    width: 100%;
  }
}

#oroload .orl__fill { opacity: .09; }

/* ── the footer sitemap was 4.4 screens tall on a phone ──────────────────────
   Measured at 390x844: footer height 3733px = 4.4 full viewports, across seven
   columns and about forty links.

   The cost is almost entirely per-row. oro-mobile.css gives every `.footer a` a
   44px min-height under (pointer: coarse), and oro-footer.css adds a 9px margin
   below each <li> — so each link occupies 53px whether or not its text needs it.
   Forty of those is roughly 2,100px of nothing but tap padding.

   44px is the Apple/Material platform guideline. WCAG 2.5.8 AA asks 24x24, and
   explicitly relaxes even that where targets are adequately spaced. A 36px row
   clears the AA floor by half again.

   The trade is made back on the OTHER axis: these links were inline-flex, so
   only the text was tappable — "Slot Market" was a ~90px target in a 320px row.
   Making the row a full-width flex target gives roughly 3x the hit area, so the
   shorter row is EASIER to hit than the tall one it replaces, not harder.

   Scoped to coarse pointers at <=1024px, so the desktop footer is untouched. */
@media (pointer: coarse) and (max-width: 1024px) {

  /* full-width target, shorter row */
  /* SPECIFICITY, and why it is this ugly. The rule to beat is in
     oro-mobile.css and reads:
       .footer a, .orof__status-link, .app-button,
       .orox a:not([class*="btn"]):not([class*="cta"]) { min-height: 44px }
     Each selector in a list is weighed on its own, and the LAST one matches
     these links at (0,3,2-ish): .orox plus two :not() attribute clauses.
     A plain `.orof__list a` is (0,1,1) and loses even from a later layer,
     which is why the first two attempts at this changed nothing.
     The footer section carries BOTH .orox and .orof, so chaining them gets
     to (0,3,2) honestly, without !important. */
  .orox.orof .orof__list li a {
    display: flex;
    width: 100%;
    min-height: 36px;
    align-items: center;
  }

  /* the min-height now carries the rhythm; the margin was doubling up */
  .orox.orof .orof__list li { margin-bottom: 2px; }

  /* column heading sat 13-20px off its list */
  .orox.orof .orof__ct { margin-bottom: 9px; }

  /* 32px top and bottom on every one of seven cards */
  .orox.orof .orob { padding-top: 16px; padding-bottom: 16px; }

  /* the "SOON" rows are not links and kept their own looser spacing */
  .orox.orof .orof__soon { min-height: 32px; margin-bottom: 2px; }
}

/* ── the drawer ran under the phone's address bar ────────────────────────────
   Reported as "menu bar in mobile having scroll issue".

   .orom-drawer is `position: fixed; inset: 0`, which resolves against the
   LAYOUT viewport. On a phone that is the tall measurement — the one that
   assumes the address bar is hidden. While the bar IS showing, the visible area
   is roughly 60-110px shorter, so the drawer's foot (112px, and it holds the
   CTA) sits underneath the browser chrome, and the scrollable body sizes itself
   against a height the reader cannot actually see. Measured in the pane: the
   panel's three children sum to 833.9px inside an 844px box, and the panel
   itself reports scrollHeight 846 against clientHeight 844 with overflow
   hidden — 2px permanently clipped even on a desktop-sized viewport, which is
   the same bug in miniature.

   100dvh is the dynamic viewport height: it tracks the browser chrome as it
   shows and hides, so the drawer is always exactly as tall as what the reader
   can see. With top/bottom/height all set, the spec drops `bottom`, so this
   cleanly overrides the inset without touching it.

   Progressive by construction: a browser without dvh support ignores the
   declaration entirely and keeps today's behaviour. oro-sections.css already
   uses svh, so dynamic units are established in this codebase. */
@media (max-width: 1024px) {
  .orom-drawer { height: 100dvh; }
}

/* ── a dropped script must not be able to blank most of the site ─────────────
   index.html adds `html.orox-js` from a BLOCKING inline <script> in <head>, and
   oro-sections.css:600 then sets opacity:0 on every .orox-reveal. The only
   thing that ever shows them again is assets/js/oro-sections.js — 44 KB at the
   very end of a ~9.9 MB document. If that one request fails, thirteen orox
   sections stay at opacity 0 forever: chapters two through five, most of the
   site's words, rendered as blank screens. Reproduced at 390x844 — 30 of 30
   visible nodes in chapter two computed to opacity 0.

   On a flaky mobile plan that is a white page, not a degraded one, and it is
   the likeliest explanation for "cannot load the site on mobile data".

   Re-hang the hiding on `.orox.is-ready`, the class oro-sections.js adds as the
   very first thing it does (line 25) before any observer is wired. Script
   present: byte-identical behaviour. Script missing: nothing is ever hidden and
   the page reads as plain, unanimated content.

   The four rules are a specificity ladder and all four are required:
     1 (0,2,0) beats oro-sections.css:600 on load order — default is visible.
     2 (0,3,0) restores the hiding, but only once the script has run.
     3 (0,4,0) MUST exist: rule 2 out-ranks `.orox-reveal.is-in` (0,2,0), which
       is what the observer sets to reveal a section. Without this the site
       would hide everything permanently — the exact failure being fixed.
     4 oro-sections.css:643 un-hides under reduced motion at (0,2,0); rule 2
       would silently out-rank that too, so it is restored at (0,3,0). */
.orox-js .orox-reveal { opacity: 1; transform: none; }
.orox-js .orox.is-ready .orox-reveal { opacity: 0; transform: translateY(18px); }
.orox-js .orox.is-ready .orox-reveal.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .orox-js .orox.is-ready .orox-reveal { opacity: 1; transform: none; }
}

/* ── the 146px void under the app bar, at <=479 only ─────────────────────────
   My own regression, and worth naming as such. Earlier in this project I gave
   .hero-text-wrapper a margin-top of clamp(84px,7vw,108px) so the hero eyebrow
   would clear the dynamic island — measured at 710 and 1440 wide, where the
   island is real.

   But oro.css, inside @media (max-width:479px), sets .hero-phone-island to
   display:none. Below 480 the island is not on the page at all — .oroi-pill
   measures 0x0 at opacity 0 at 360, 390, 414, 430 and 479 — so the clearance
   pays for a collision that cannot happen, and it is the top 84px of the empty
   band under the bar.

   Measured at 390x844: eyebrow top 202 -> 118, gap below the bar 146px -> 62px.
   At 360x800: 196 -> 112, gap 140px -> 56px. An ink profile confirms the band
   is genuinely unpainted: 0.0% ink in every 35px slice from y=35 to y=176.

   If the island is ever re-enabled below 480, revert this with it. */
@media (max-width: 479px) {
  .hero-text-wrapper { margin-top: 0; }
}
