/* report/shared/app-light.css — the App 1.2 light look (plan of record:
   dev/APP-1.2-BUILD-PLAN-2026-09-30.md, section 4.6).

   APP ONLY. Every selector in this file starts with html.o2i-store, a class
   that only the native app sets (the store-mode detection script at the top of
   report/instagram/index.html). The website never matches a rule here, so it
   renders exactly as it did before this file existed.
   tests/research/app-light.test.js parses this file and fails the gate if a
   selector does not start with html.o2i-store.

   Linked from report/instagram/index.html AFTER report-design.css, because
   report-design.css already sets three tokens on html.o2i-store at the same
   specificity (--fg-4 and the two --line-*), and the later rule wins.

   Four blocks, as the plan names them:
     1. the app's tokens, on html.o2i-store;
     2. the dark restore, on the report's own roots, so an open report's body
        looks exactly as it does today;
     3. which screen is showing (a note, no rule: see below);
     4. the chat, light again with or without a report under it.
   Then the base look that belongs to no component file. */

/* ── Block 1: the app's tokens ───────────────────────────────────────────
   The base tokens the components already read, and the app-only --app-*
   tokens. Shared files (the ones /me/ loads too) write an app color as
   var(--app-NAME, <the literal it was>): the --app-* names exist only here, so
   on the website the literal still applies. Three parallel packages use these
   exact names; do not rename them. */
html.o2i-store {
  color-scheme: light;

  --bg-0: #ececee;
  --bg-1: #f7f7f8;
  --bg-2: #ffffff;
  --bg-3: #f2f2f4;
  --fg-1: #111111;
  --fg-2: #3a3a3c;
  --fg-3: #606065;
  --fg-4: #66666b;
  --line-1: rgba(0,0,0,.06);
  --line-2: rgba(0,0,0,.12);
  --serif: "Inter", system-ui, sans-serif;
  --sans: "Inter", system-ui, sans-serif;
  --grad: linear-gradient(135deg,#3DDDAA,#6B6AF0);

  --app-page: radial-gradient(ellipse at 50% 18%, #f7f7f8 0%, #ececee 55%, #dcdce0 100%);
  --app-card: rgba(255,255,255,.78);
  --app-card-line: rgba(0,0,0,.05);
  --app-card-radius: 24px;
  --app-card-shadow: 0 12px 30px rgba(0,0,0,.05);
  --app-ink: #111111;
  --app-ink-2: #3a3a3c;
  --app-ink-3: #606065;
  --app-ink-4: #66666b;
  --app-line: rgba(0,0,0,.06);
  --app-field: #ffffff;
  --app-pill: #111111;
  --app-pill-ink: #ffffff;
  --app-ghost: rgba(255,255,255,.7);
  --app-mint-ink: #0a7458;
  --app-red-ink: #c0352b;
  --app-font: "Inter", system-ui, sans-serif;
}

/* ── Block 2: the dark restore on the report's own roots ──────────────────
   #report-output and .r2-app (the dashboard), .v2-story (the story deck) and
   .lightbox-real (the enlarged step image). Every token the report reads goes
   back to the value it has in the app today: the :root values of
   report-design.css and index.html, with the store's own --fg-4 and --line-*
   from report-design.css's html.o2i-store block (B40). --serif, --sans,
   --grad and --mint are stated even where block 1 leaves one alone, so no
   later token change can leak into the report. Every --app-* token is set to
   initial, so inside the report a var(--app-X, literal) falls back to its
   literal, exactly as it does on the website. */
html.o2i-store #report-output,
html.o2i-store .r2-app,
html.o2i-store .v2-story,
html.o2i-store .lightbox-real {
  color-scheme: dark;

  --bg-deep: #0c0b09;
  --bg-0: #0f0e0c;
  --bg-1: #15130f;
  --bg-2: #1c1914;
  --bg-3: #242019;
  --fg-1: #f2f0ec;
  --fg-2: #c9c4bc;
  --fg-3: #9a9590;
  --fg-4: #8d8880;
  --line-1: rgba(242,240,236,0.14);
  --line-2: rgba(242,240,236,0.22);
  --purple: #6b6af0;
  --purple-light: #8a89f5;
  --mint: #3dddaa;
  --mint-light: #7ee8c9;
  --amber: #e8b923;
  --caution: #f0913e;
  --red: #f87171;
  --grad: linear-gradient(135deg,#6b6af0,#3dddaa);
  --serif: "Cormorant Infant",Georgia,serif;
  --sans: "Inter",system-ui,sans-serif;
  --mono: "JetBrains Mono",ui-monospace,monospace;

  --app-page: initial;
  --app-card: initial;
  --app-card-line: initial;
  --app-card-radius: initial;
  --app-card-shadow: initial;
  --app-ink: initial;
  --app-ink-2: initial;
  --app-ink-3: initial;
  --app-ink-4: initial;
  --app-line: initial;
  --app-field: initial;
  --app-pill: initial;
  --app-pill-ink: initial;
  --app-ghost: initial;
  --app-mint-ink: initial;
  --app-red-ink: initial;
  --app-font: initial;
}

/* ── Block 3: which screen is showing ────────────────────────────────────
   r2-active stays on <html> for as long as a report is mounted, including
   while Home, Grade, Reports and Profile are drawn over it (o2i-surface is the
   class that says a tab surface covers the report; v2-shell-store.jsx). So
   NOTHING in this file is keyed on r2-active alone. The tab surfaces, the tab
   bar, the header and every .v2sh-* sheet live in #v2-shell-root, a child of
   <body> outside every block-2 root, so they inherit block 1 always and are
   light whether or not a report is mounted under them. Only the report's own
   body is dark, and only through block 2. */

/* ── Block 4: the chat ───────────────────────────────────────────────────
   The Chat tab draws the report's own dock full screen (html.o2i-chat-full,
   set by the shell) when a report is mounted, and that dock sits inside
   #report-output, so block 2 would make it dark. Its own element takes block
   1's tokens back. The second selector carries an id, so it out-ranks block 2
   even on an element both could match. With no report, the shell's own dock
   is outside the report roots and already light. */
/* NO DARK FLASH ON OPEN OR CLOSE (2026-10-12, Robert: "when I click on the
   chat, it flickers a little bit"). With the tab bar up, the dock is only ever
   shown as the Chat tab's takeover. It mounts a frame or two before the shell
   sets o2i-chat-full, and in that gap block 2 painted it as the report's dark
   floating card. Hidden until the shell marks it. */
html.o2i-store.o2i-tabbar:not(.o2i-chat-full) .v2-chat-dock { visibility:hidden; }
html.o2i-store.o2i-chat-full .v2-chat-dock,
html.o2i-store.o2i-chat-full #report-output .v2-chat-dock {
  color-scheme: light;

  --bg-0: #ececee;
  --bg-1: #f7f7f8;
  --bg-2: #ffffff;
  --bg-3: #f2f2f4;
  --fg-1: #111111;
  --fg-2: #3a3a3c;
  --fg-3: #606065;
  --fg-4: #66666b;
  --line-1: rgba(0,0,0,.06);
  --line-2: rgba(0,0,0,.12);
  --serif: "Inter", system-ui, sans-serif;
  --sans: "Inter", system-ui, sans-serif;
  --grad: linear-gradient(135deg,#3DDDAA,#6B6AF0);

  --app-page: radial-gradient(ellipse at 50% 18%, #f7f7f8 0%, #ececee 55%, #dcdce0 100%);
  --app-card: rgba(255,255,255,.78);
  --app-card-line: rgba(0,0,0,.05);
  --app-card-radius: 24px;
  --app-card-shadow: 0 12px 30px rgba(0,0,0,.05);
  --app-ink: #111111;
  --app-ink-2: #3a3a3c;
  --app-ink-3: #606065;
  --app-ink-4: #66666b;
  --app-line: rgba(0,0,0,.06);
  --app-field: #ffffff;
  --app-pill: #111111;
  --app-pill-ink: #ffffff;
  --app-ghost: rgba(255,255,255,.7);
  --app-mint-ink: #0a7458;
  --app-red-ink: #c0352b;
  --app-font: "Inter", system-ui, sans-serif;
}
/* The dock's text that sets no color of its own inherits a COMPUTED color,
   and inside a mounted report that is the report's light-on-dark ink, not a
   token. So the dock states its own. */
html.o2i-store.o2i-chat-full .v2-chat-dock,
html.o2i-store.o2i-chat-full #report-output .v2-chat-dock {
  color: var(--fg-1);
}
/* The dock's two colored inks, on the light look (P6 review, 2026-09-30).
   report-design.css draws the error line in --red on a 10% red wash and the
   user's own bubble white on --purple; with the dark values those measured
   2.51 and 4.25 to 1 in the app, with a report and without one. The dock
   takes a darker pair here: #c0352b on the wash is 5.04 to 1, and white on
   #5857e0 is 5.45 to 1. The no-report dock lives in #v2-shell-root. */
html.o2i-store.o2i-chat-full .v2-chat-dock,
html.o2i-store.o2i-chat-full #report-output .v2-chat-dock,
html.o2i-store #v2-shell-root .v2-chat-dock {
  --red: #c0352b;
  --purple: #5857e0;
}

/* ── Interim dark islands: retired (App 1.2, package P6) ─────────────────
   The weekly carousel card (.v2rs-cc), the mint streak strip (.v2rs-strip),
   the gold badge (.v2rs-badge.gold) and the post-sign-in welcome moment
   (.o2i-moment) each kept a dark token here until its owner restyled it. None
   of them mounts on the app's Home any more (the carousel, the streak strip
   and the study banner left it; P5 retired the Moment in the app), and the
   research file writes the gold badge's ink as var(--app-ink, ...) itself, so
   the islands are gone. */

/* The study rows' units over the photo veil (.v2rs-row-art .v2rs-unit) kept
   a light ink here until app round 5. Those rows are drawn only by the
   Studies list, which the app no longer mounts (the website's /me/ does,
   and this file never reaches it), so the rule left with them. */

/* The notifications pre-prompt (v2-native.js prePrompt) reads the app's own
   --app-* tokens since App 1.2 (package P6), so the interim --t-1 token that
   kept its text readable is gone. */

/* ── The base look ───────────────────────────────────────────────────────
   Only what belongs to no component file. */

/* The page behind the app's surfaces: the mockup's light gray radial. It is
   pinned to the viewport so a long start screen does not stretch it; the
   color under it is the page's own --bg-0 (block 1), so a device that ignores
   the attachment still shows the right gray. The report's roots draw their
   own dark background over it (block 2). */
html.o2i-store body {
  background-color: var(--bg-0);
  background-image: var(--app-page);
  background-repeat: no-repeat;
  background-attachment: fixed;
  color: var(--app-ink);
  font-family: var(--app-font);
}

/* The app's own containers: default text color and font. The tab shell, the
   welcome and sign-in screen, the start screen, the legal sheet and the
   analyzing cover. Components still set their own type; this is what text with
   no rule of its own inherits. */
html.o2i-store #v2-shell-root,
html.o2i-store #upload-view,
html.o2i-store #o2i-welcome,
html.o2i-store #o2i-legal,
html.o2i-store #o2i-analyzing {
  color: var(--app-ink);
  font-family: var(--app-font);
}

/* Form controls in the light look, outside the report. The website's form
   controls are untouched (this is store-only), and the report's own inputs
   keep their dark look: the :not() leaves every block-2 root alone. */
html.o2i-store :where(input, textarea, select):not(:where(#report-output, .r2-app, .v2-story, .lightbox-real) *) {
  accent-color: var(--app-ink);
  caret-color: var(--app-ink);
}
html.o2i-store :where(input[type=text], input[type=email], input[type=tel], input[type=number], input[type=search], input:not([type]), textarea, select):not(:where(#report-output, .r2-app, .v2-story, .lightbox-real) *)::placeholder {
  color: var(--app-ink-3);
  opacity: 1;
}

/* Focus rings: one dark ring on the light screens, visible on white and on the
   page gray alike. Keyboard focus only (:focus-visible), so a tap draws
   nothing. Not inside the report, whose focus look is unchanged. */
html.o2i-store :where(a, button, input, textarea, select, summary, [tabindex]):not(:where(#report-output, .r2-app, .v2-story, .lightbox-real) *):focus-visible {
  outline: 2px solid var(--app-ink);
  outline-offset: 2px;
}

/* Mint as text. The brand mint (#3DDDAA) is 1.6:1 on white, so text never
   uses it in the light look: text that means "mint" uses --app-mint-ink
   (3.4:1 on white, large text and icons only), and small text that used to be
   mint uses --app-ink. .o2i-mint-ink is the one shared hook for a component
   that wants mint text without writing the literal. */
html.o2i-store .o2i-mint-ink { color: var(--app-mint-ink); }

/* Text controls are ink, never mint (App 1.2, plan 4.6; package P6). The
   account file's full-screen screens (About you, Payment, the one-field
   editors) close with a word drawn in --app-mint-ink. In the app it is the
   page's ink, like the shell's own Close and Back, so every screen closes the
   same way. The report's roots keep their own look. (The profile head's Edit
   word had a rule here too; that head is mounted nowhere since App 1.2, so
   the rule left in app round 5.) */
html.o2i-store .v2acct-screen-x:not(:where(#report-output, .r2-app, .v2-story, .lightbox-real) *) {
  color: var(--app-ink);
}

/* ── No zoom, no sideways drag (build 64, 2026-09-30) ────────────────────
   Robert on the phone: "I was able to zoom in and zoom out ... it completely
   messed up the framing ... it needs to be more static." The viewport is
   locked in the app branch of report/instagram/index.html; this is the other
   half. pan-x pan-y keeps every scroll and swipe the app has and drops the
   two zoom gestures (pinch, double tap). It is deliberately on the root only:
   touch-action narrows down the tree, so a carousel or a sheet inside still
   scrolls the way it did. The website never gets this (html.o2i-store). */
html.o2i-store,
html.o2i-store body {
  touch-action: pan-x pan-y;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-x: none;
}

/* ── A back control you can see (APP ROUND 6) ─────────────────────────────
   Robert on build 66, on the About you page: "we do need some type of back
   button". Every pushed page had one, drawn as small gray text with a thin
   chevron, which reads like a caption. This is the ONE look for all of them:
   a 38px white round button with the card's soft shadow holding an ink
   chevron, then the label in ink at 16px semibold, the whole control at
   least 44px tall for the thumb. One rule names every back class the app's
   pushed pages use:
     .v2sh-back      the shell's pushed pages (Notifications, iMessage alerts,
                     Help and its answers, Research panel);
     .v2acct-back    About you (v2-account-store.jsx);
     .v2rs-s1-back   the study page (v2-research.jsx);
     .v2ag-app-back  a graded ad's answers page and the grading run
                     (v2-ads-grade.jsx).
     .v2r-app-back   the study run's top Back beside '1 of 6' (v2-research.jsx;
                     on question 1 it closes the run).
   The circle is ::before and the chevron ::after; each file's own chevron
   (an svg, a span, or a ::before) is hidden or redrawn here. The :root in
   each selector lifts it over the files' own two-class rules
   (.v2rs.store .v2rs-s1-back, .v2ag-app.v2ag-a .v2ag-app-back,
   .v2r-ov.v2r-app .v2r-app-back) without an
   !important. App only, like every rule in this file. */
html.o2i-store:root .v2sh-back,
html.o2i-store:root .v2acct-back,
html.o2i-store:root .v2rs-s1-back,
html.o2i-store:root .v2ag-app-back,
html.o2i-store:root .v2r-app-back {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0;
  min-height: 44px;
  margin: 0;
  padding: 0 10px 0 48px;
  background: none;
  border: none;
  font: 600 16px/1 var(--app-font, Inter, system-ui, sans-serif);
  color: var(--app-ink, #111111);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
html.o2i-store:root .v2sh-back::before,
html.o2i-store:root .v2acct-back::before,
html.o2i-store:root .v2rs-s1-back::before,
html.o2i-store:root .v2ag-app-back::before,
html.o2i-store:root .v2r-app-back::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 38px;
  height: 38px;
  margin: -19px 0 0 0;
  box-sizing: border-box;
  border-radius: 50%;
  border: 1px solid var(--app-card-line, rgba(0,0,0,.05));
  background: #ffffff;
  box-shadow: var(--app-card-shadow, 0 12px 30px rgba(0,0,0,.05)), 0 1px 3px rgba(0,0,0,.08);
  transform: none;
}
html.o2i-store:root .v2sh-back::after,
html.o2i-store:root .v2acct-back::after,
html.o2i-store:root .v2rs-s1-back::after,
html.o2i-store:root .v2ag-app-back::after,
html.o2i-store:root .v2r-app-back::after {
  content: "";
  position: absolute;
  left: 16px;
  top: 50%;
  width: 9px;
  height: 9px;
  margin: -5.5px 0 0 0;
  box-sizing: border-box;
  border-left: 2.2px solid var(--app-ink, #111111);
  border-bottom: 2.2px solid var(--app-ink, #111111);
  transform: rotate(45deg);
}
html.o2i-store:root .v2sh-back svg,
html.o2i-store:root .v2ag-app-back .v2ag-app-chev {
  display: none;
}
html.o2i-store:root .v2sh-back:active,
html.o2i-store:root .v2acct-back:active,
html.o2i-store:root .v2rs-s1-back:active,
html.o2i-store:root .v2ag-app-back:active,
html.o2i-store:root .v2r-app-back:active {
  opacity: 0.6;
}

/* ── No scroll bar, and nothing moves on a screen that fits (build 70, N34) ─
   Robert on build 69: "on some screen it lets me scroll down so the bar to the
   right pops up especially when there is nothing to scroll it should have a
   native app feel". Two halves, both app only:
     1. No visible scroll bar anywhere in the app: the page and every inner
        scroller (the tab surfaces, the pushed screens, the sheets, the
        report's own panes). A screen taller than the phone still scrolls; it
        just draws no bar, as a native list does.
     2. No rubber-band pull on the root. The tab surfaces and pushed screens
        are fixed layers that scroll themselves (overscroll-behavior: contain
        in the shell's CSS); a drag on one whose content fits used to chain to
        the page and pull the whole web view. The root takes none, so a
        screen that fits does not move at all. While a tab surface is up
        (html.o2i-surface, the shell's marker) this outranks report-design.css's
        html.o2i-store.r2-active rule, which gives an OPEN report its bounce
        back (Robert, 2026-07-27): the report on screen keeps that bounce, a
        tab over it does not. Each package removes the extra
        height of its own screens at its source, so a screen that fits has no
        scroll range to begin with (the shell's are measured in its handover).
   The website never matches (html.o2i-store). */
html.o2i-store,
html.o2i-store * {
  scrollbar-width: none;
}
html.o2i-store ::-webkit-scrollbar {
  display: none;
  width: 0;
  height: 0;
  background: transparent;
}
html.o2i-store,
html.o2i-store body,
html.o2i-store.o2i-surface:root,
html.o2i-store.o2i-surface:root body {
  overscroll-behavior-y: none;
}
/* Build 70 fix round (N34, the open report): the report the app opens is the
   deck, a fixed layer with its own snap scroller (overscroll-behavior:
   contain), so its bounce at the first and last card is its own and stays.
   The deck marks the root o2i-deck while it is mounted, and that root takes
   no rubber-band either, so a drag on the deck's header or on a card that
   fits no longer pulls the whole web view and shows the bar. This outranks
   report-design.css's html.o2i-store.r2-active rule (Robert's build-5 bounce
   answer, given for the old scrolling dashboard). The phone's own
   alwaysBounceVertical (ios/App/App/AppDelegate.swift) is now false
   (release 1.2, Robert 2026-10-02: "Yes, turn off the bounce on the screen
   to fit"), so a screen that fits the phone no longer moves natively either;
   bounces stays on for a screen taller than the phone. This rule stays as the
   web-side half. */
html.o2i-store.o2i-deck:root,
html.o2i-store.o2i-deck:root body {
  overscroll-behavior-y: none;
}
