﻿/* ════════════════════════════════════════════════════════════════════
   STUDENT PORTAL — integration glue. Hand-written.
   ev-portal.css beside this file is GENERATED from the Claude Design
   prototype and holds every component; this file only fits that design
   into a real page: the prototype scrolls inside a #device frame and
   positions things against it, the app scrolls the document.
   Linked by Pages/Shared/Octo/_PortalHead.cshtml, after ev-portal.css.
   ════════════════════════════════════════════════════════════════════ */

/* The generated sheet hides [hidden] only INSIDE an .ev-scope; a dialog declared in a page's
   @section Modals is itself the scope, and .ev-scrim's display:flex would otherwise win. */
[hidden] { display: none !important; }

/* What the prototype sets on <body>. On .ev-scope, not on body, so the student pages that still
   wear Kendo keep Octo's type scale until they are ported. */
.ev-scope {
  color: var(--ev-text-body);
  font: 400 15px/1.55 var(--ev-font-body);
  -webkit-font-smoothing: antialiased;
  -webkit-tap-highlight-color: transparent;
}
body.octo-portal-body { background: var(--ev-ink-50); }

/* The shared layout's container IS the design's .wrap (1160px, the --ev-gut gutter), and the design's
   .ev-page brings its own vertical rhythm — so the layout's own padding stands down. */
.octo-portal-container { max-width: 1160px; padding-inline: var(--ev-gut); }
.octo-portal-main { padding-block: 0; }

/* "The status header must be in EVERY screenshot, so it never scrolls away." */
.ev-hdr { position: sticky; top: 0; z-index: 40; }
[id] { scroll-margin-top: 76px; }

/* Bottom tabs: pinned to the viewport; the page leaves room for them. Hidden from 900px (generated). */
#evTabbar { position: fixed; inset-inline: 0; bottom: 0; }
@media (max-width: 899px) {
  body.octo-portal-body { padding-bottom: calc(60px + env(safe-area-inset-bottom, 0px)); }
}

/* The overlay layer covers the viewport; only what is in it takes clicks. */
#evLayer { position: fixed; inset: 0; pointer-events: none; z-index: 60; }
#evLayer > * { pointer-events: auto; }
#evLayer > .ev-toasts { pointer-events: none; }
body.ev-modal-open { overflow: hidden; }

/* The language control is a <form> here (it posts the choice); the design draws a <div>. */
form.ev-lang { margin: 0; }

/* Course icons are uploaded images, not icon names: the image fills the design's icon box. */
.ev-cicon img { width: 100%; height: 100%; object-fit: contain; border-radius: inherit; display: block; }
.ev-cicon.ev-img { background: var(--ev-surface-card); color: inherit; padding: 4px; }

/* ── Lesson viewer: the prototype draws mock players; the app embeds the real thing ── */
/* The sticky chip strip sits under the sticky header; the question pager sits above the tab bar. */
.ev-typebar { top: 60px; }
@media (max-width: 899px) {
  .ev-qnav { bottom: calc(60px + env(safe-area-inset-bottom, 0px)); }
}
/* Vimeo fills the design's player box. */
.ev-player iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* A comic bundle in its own frame (the design's comic spread proportions). */
.ev-frame { position: relative; border-radius: var(--ev-radius-lg); overflow: hidden; background: var(--ev-ink-900); }
.ev-comicframe { aspect-ratio: 4 / 5; max-height: 72vh; margin-inline: auto; width: 100%; }
.ev-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; background: var(--ev-surface-card); }
/* Sketchfab fills the design's viewport box; the two bars mask its own title and share rows, as the
   old page did. */
.ev-model .ev-vp iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.ev-model .ev-vp .ev-bar-top, .ev-model .ev-vp .ev-bar-bottom { position: absolute; inset-inline: 0; height: 44px; background: var(--ev-ink-900); }
.ev-model .ev-vp .ev-bar-top { top: 0; }
.ev-model .ev-vp .ev-bar-bottom { bottom: 0; }
/* PDF pages are canvases from pdf.js: paper-white, stacked, scaled by the toolbar's zoom. */
.ev-pdf .ev-pg { --ev-zoom: 1; display: flex; flex-direction: column; align-items: center; gap: 12px; }
.ev-pdfpage { width: calc(min(100%, 720px) * var(--ev-zoom)); max-width: none; background: var(--ev-surface-card); box-shadow: var(--ev-shadow-md); }
.ev-pdfpage canvas { display: block; width: 100%; height: auto; }

/* ── Checkout: real form controls inside the design's boxes ── */
/* The country code is a select, where the prototype had a fixed prefix. */
.ev-ctl select.ev-phonecode { flex: 0 0 auto; width: auto; max-width: 11em; padding-inline-end: 8px; margin-inline-end: 4px; border-inline-end: 1px solid var(--ev-border-subtle); border-radius: 0; font: 500 14px var(--ev-font-mono); color: var(--ev-ink-600); }
/* The quantity is a number input, where the prototype had an <output>. */
.ev-qty input { width: 52px; min-width: 36px; height: 44px; text-align: center; border: 0; background: none; font: 600 15px var(--ev-font-mono); color: var(--ev-text-strong); -moz-appearance: textfield; appearance: textfield; }
.ev-qty input::-webkit-outer-spin-button, .ev-qty input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* The tier choice is a real radio inside the design's card. */
label.ev-pkg { cursor: pointer; }
label.ev-pkg input { position: absolute; opacity: 0; pointer-events: none; }
/* asp-validation-for renders its message into the .ev-err span; empty = no space taken. */
.ev-err:empty { display: none; }
.ev-check.ev-bad .ev-box { border-color: var(--ev-ember-600); }
/* Pay bar above the phone tab bar. */
@media (max-width: 899px) {
  .ev-paybar { bottom: calc(60px + env(safe-area-inset-bottom, 0px)); }
}

/* ── Public pages (the landing page, Privacy) ── */
body.octo-public-body { background: var(--ev-surface-card); }

/* A brand whose logo already carries its name (docs/brand-package.md) is inlined on its own instead of the mark
   plus a text wordmark. It is wider than the mark, so it is sized by height; ev-portal.css sizes svg by width. */
.ev-brand .ev-lockup { width: auto; height: 32px; }
.ev-fbrand .ev-brand .ev-lockup, .ev-ftr .ev-brand .ev-lockup { height: 26px; }
.ev-auth-side .ev-brand .ev-lockup { height: 38px; }

/* The hero's phone miniature draws the portal header at a quarter size, so the brand inside it follows. */
.ev-mp-brand .ev-lockup { width: auto; height: 16px; }
.ev-mp-brand span { font: 700 11px var(--ev-font-display); letter-spacing: -.02em; }
/* The landing page lays itself out edge to edge; the shared public main adds no padding of its own. */
.octo-public-main { display: flex; flex-direction: column; }
.octo-public-main > .ev-ld { flex: 1; }
/* The public header is the design's: sticky like the portal's, its section links hidden on a phone. */
.ev-hdr.ev-pub { position: sticky; top: 0; z-index: 40; }
.ev-hdr.ev-pub .ev-nav a { display: inline-flex; align-items: center; padding: 9px 12px; border-radius: var(--ev-radius-sm); font: 500 14px var(--ev-font-body); color: var(--ev-text-muted); }
.ev-hdr.ev-pub .ev-nav a:hover { background: var(--ev-ink-50); color: var(--ev-text-strong); }
.ev-hdr.ev-pub .ev-monly { display: inline-flex; }
@media (min-width: 900px) { .ev-hdr.ev-pub .ev-monly { display: none; } }
/* A section link lands under the sticky header, not behind it. */
.ev-ld [id] { scroll-margin-top: 72px; }
/* The mini phone's course names inherit the tile's tiny type. */
.ev-mp-tt .ev-ar { font: inherit; }
/* Feature titles carry a "coming soon" tag inline. */
.ev-ld-featbody h3 .ev-newtag { vertical-align: middle; margin-inline-start: 6px; }

/* ── Sign in and register: the design's auth shell on the shared auth layout ──
   The layout gives the two panes (.auth-brand + .auth-form-panel); the project's Octo/_AuthBrand and
   the page bodies fill them with the design's markup. */
.auth-brand { align-items: stretch; justify-content: flex-start; padding: 0; }
.auth-brand::before, .auth-brand::after { display: none; }
.ev-auth-side { position: relative; overflow: hidden; display: flex; flex-direction: column; gap: 16px; flex: 1; padding: 18px var(--ev-gut) 24px; color: var(--ev-ink-200); font: 400 15px/1.55 var(--ev-font-body); }
.ev-auth-side > * { position: relative; }
.ev-auth-side h2 { color: #fff; font: 700 23px/1.2 var(--ev-font-display); letter-spacing: -.02em; }
.ev-auth-side p { font-size: 14.5px; color: var(--ev-ink-300); }
.ev-auth-side .ev-ld-ring { position: absolute; top: -70px; left: auto; inset-inline-end: -150px; margin: 0; width: 380px; height: 380px; }
.ev-auth-side .ev-ld-blade { position: absolute; top: auto; bottom: 0; }
.ev-perks { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 10px; font-size: 14.5px; color: var(--ev-ink-200); }
.ev-perks li { display: flex; gap: 10px; align-items: flex-start; }
.ev-perks .ev-ic { color: var(--ev-meadow-400); margin-top: 3px; }
.ev-auth-side .ev-cp { color: var(--ev-ink-400); margin-top: auto; }
@media (min-width: 769px) {
  .ev-auth-side { justify-content: space-between; padding: 36px; }
  .ev-auth-side h2 { font-size: 34px; line-height: 1.12; letter-spacing: -.025em; }
  .ev-auth-side p { font-size: 16px; }
  .ev-auth-side .ev-ld-ring { width: 620px; height: 620px; top: auto; bottom: -190px; inset-inline-end: -260px; }
  .ev-auth-side .ev-ld-blade { bottom: 120px; }
}
@media (max-width: 768px) {
  .ev-auth-side .ev-perks, .ev-auth-side .ev-cp { display: none; }
}
.auth-form-panel { background: var(--ev-ink-50); }
.auth-form-container { max-width: 520px; }
/* Setup: the password requirements checklist is rendered from the configured Identity policy; each
   item turns green / red as the user types. */
.setup-password-rules { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.setup-password-rules li { display: flex; gap: 6px; align-items: center; }
.setup-password-rules li::before { content: "2"; width: 10px; text-align: center; }
.setup-password-rules li.match { color: var(--octo-success); }
.setup-password-rules li.match::before { content: "¹3"; }
.setup-password-rules li.mismatch { color: var(--octo-error); }
.setup-password-rules li.mismatch::before { content: "¹5"; }
.ev-fcard { display: flex; flex-direction: column; gap: 18px; }
.ev-fcard h1 { font-size: 24px; font-weight: 700; }

/* ── AI hub (Pages/Students/AI, AIGeneration) — hand-written glue for the generated classes ──────────────────
   The generators' icons are Font Awesome classes (the admin picks them), drawn inside the design's icon bubbles. */
.ev-scope .ev-cicon > i { font-size: 18px; line-height: 1; }
.ev-scope .ev-gtab > .ev-ic { display: inline-grid; place-items: center; width: 20px; height: 20px; color: var(--ev-c); }
.ev-scope .ev-gtab > .ev-ic > i { font-size: 16px; line-height: 1; }
.ev-scope .ev-gtab[aria-selected=true] > .ev-ic { color: #fff; }
.ev-scope .ev-step h2 { display: flex; align-items: center; gap: 10px; }
.ev-scope .ev-gen-credits { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 13.5px; color: var(--ev-text-muted); }
/* The balance a student checks before generating: readable at a glance, and plainly a warning when it runs low. */
.ev-scope .ev-credit-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border-radius: 999px; background: var(--ev-cobalt-50); color: var(--ev-cobalt-700); border: 1px solid var(--ev-cobalt-100); font: 600 13.5px/1.2 var(--ev-font-body); }
.ev-scope .ev-credit-chip.ev-low { background: var(--ev-sun-50); color: var(--ev-sun-700); border-color: var(--ev-sun-100); }
/* The microphone in the chat composer: quiet until pressed, then it breathes while it listens. */
.ev-scope .ev-mic { color: var(--ev-text-faint); }
.ev-scope .ev-mic.on { color: var(--ev-ember-600, #dc2626); background: color-mix(in srgb, var(--ev-ember-600, #dc2626) 12%, transparent); animation: ev-mic-pulse 1.2s ease-in-out infinite; }
@keyframes ev-mic-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, .35); } 50% { box-shadow: 0 0 0 6px rgba(220, 38, 38, 0); } }
@media (prefers-reduced-motion: reduce) { .ev-scope .ev-mic.on { animation: none; } }

/* "How was this?" — stars that read as a control, not as decoration, and the box that opens under a low one.
   The fill is cumulative and driven from script (aria-pressed), so hovering the fourth star lights the first
   four whichever way the page runs; a locked card keeps the gold and loses the pointer. */
.ev-scope .ev-rate { gap: 8px; }
.ev-scope .ev-rate-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ev-scope .ev-stars { display: flex; gap: 2px; }
.ev-scope .ev-stars button { border: 0; background: transparent; padding: 2px; cursor: pointer; line-height: 0; color: var(--ev-border-default); border-radius: 8px; }
.ev-scope .ev-stars button svg { fill: currentColor; display: block; }
.ev-scope .ev-stars button[aria-pressed="true"] { color: var(--ev-sun-500, #f59e0b); }
.ev-scope .ev-stars button:disabled { cursor: default; }
.ev-scope .ev-rate-value { font-variant-numeric: tabular-nums; }
.ev-scope .ev-rate-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ev-scope .ev-rate-said { margin: 0; font-style: italic; }
.ev-scope .ev-rate-comment { width: 100%; margin: 6px 0 8px; padding: 10px 12px; border-radius: var(--ev-radius-sm); border: 1px solid var(--ev-border-default); background: var(--ev-surface-card); color: var(--ev-text-strong); font: 400 14px/1.5 var(--ev-font-body); resize: vertical; }

/* On one generation: what is left, then what this run cost. */
.ev-scope .ev-gen-balance { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ev-scope .ev-gen-balance-left { display: inline-flex; align-items: center; gap: 6px; font: 600 13.5px/1.2 var(--ev-font-body); color: var(--ev-text-strong); }
.ev-scope .ev-gen-form .ev-field label { font: 600 13.5px/1.35 var(--ev-font-body); color: var(--ev-text-strong); }
.ev-scope .ev-gen-form .ev-ctl textarea { min-height: 84px; resize: vertical; }
.ev-scope .ev-gitem.ev-generating .ev-spin, .ev-scope .ev-spin > svg { animation: ev-spin 1s linear infinite; display: inline-block; vertical-align: middle; }
@keyframes ev-spin { to { transform: rotate(360deg); } }
.ev-scope .ev-gen-output { display: flex; flex-direction: column; gap: 10px; }
/* Not a scroller: a display formula, a table or code scrolls in its own box (shater-render.css). */
/* A rendered answer is long-form: the design's .ev-rich carries the body type, the paragraphs and the tables, and
   these fill in what markdown adds and a marketing block never has — headings, lists, rules, code. Without them the
   portal's own "h1-h4, p { margin: 0 }" reset runs every paragraph of an explanation into the next. */
.ev-scope .ev-rich :is(h1, h2, h3, h4) { margin: 1.15em 0 .45em; line-height: 1.3; font-weight: 600; color: var(--ev-text-strong); }
.ev-scope .ev-rich > :is(h1, h2, h3, h4):first-child { margin-top: 0; }
.ev-scope .ev-rich h1 { font-size: 1.3em; }
.ev-scope .ev-rich h2 { font-size: 1.2em; }
.ev-scope .ev-rich h3 { font-size: 1.1em; }
.ev-scope .ev-rich h4 { font-size: 1em; }
.ev-scope .ev-rich :is(ul, ol) { margin: .55em 0 .95em; padding-inline-start: 1.45em; }
.ev-scope .ev-rich li { margin: .3em 0; }
.ev-scope .ev-rich li > :is(p, ul, ol) { margin-block: .25em; }
.ev-scope .ev-rich hr { border: 0; border-top: 1px solid var(--ev-border-subtle); margin: 1.2em 0; }
.ev-scope .ev-rich blockquote { margin: .9em 0; padding-inline-start: 12px; border-inline-start: 3px solid var(--ev-border-default); color: var(--ev-text-muted); }
.ev-scope .ev-rich :not(pre) > code { font-family: var(--ev-font-mono); font-size: .92em; background: var(--ev-ink-50); border-radius: 4px; padding: 1px 5px; }
.ev-scope .ev-rich pre { overflow-x: auto; background: var(--ev-ink-50); border-radius: var(--ev-radius-sm); padding: 10px 12px; margin: .9em 0; }
/* The answer renderer's table scroller, given the frame the design gives its own (.ev-tscroll). */
.ev-scope .ev-rich .shater-tscroll { border: 1px solid var(--ev-border-subtle); border-radius: var(--ev-radius-sm); margin: .9em 0; }

/* ── One generation: a reading column, its outline and its actions beside it (user, 2026-10-03) ──────────────
   The full-width desktop decision kept the two reading surfaces readable — the chat is a centred 960px column, a
   lesson is capped at 88 characters — and this page was never added to it: an answer ran the whole width of the
   screen, 229 characters a line on a 1880px display. It now reads at the same measure as the rest, and the room
   that frees carries the answer's own steps and the buttons that used to sit two thousand words below. */
.ev-scope .ev-genpage { width: 100%; max-width: 1140px; margin-inline: auto; }
.ev-scope .ev-gen-co { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.ev-scope .ev-gen-main { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.ev-scope .ev-gen-outline-desk { display: none; }
.ev-scope .ev-gen-outline summary { cursor: pointer; font: 600 13.5px/1.4 var(--ev-font-body); color: var(--ev-text-strong); }
.ev-scope .ev-gen-steps { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.ev-scope .ev-gen-steps a { display: flex; align-items: flex-start; gap: 8px; padding: 6px 8px; border-radius: 8px; color: var(--ev-text-muted); text-decoration: none; font: 500 13px/1.4 var(--ev-font-body); }
.ev-scope .ev-gen-steps a:hover { background: var(--ev-ink-50); color: var(--ev-text-strong); }
.ev-scope .ev-gen-steps a[aria-current] { background: var(--ev-cobalt-50); color: var(--ev-cobalt-700); font-weight: 600; }
.ev-scope .ev-gen-stepn { flex: none; width: 20px; height: 20px; border-radius: 999px; display: grid; place-items: center; background: var(--ev-ink-100); color: var(--ev-text-muted); font: 600 11px/1 var(--ev-font-mono); }
.ev-scope .ev-gen-steps a[aria-current] .ev-gen-stepn { background: var(--ev-cobalt-600); color: #fff; }
/* A jump lands under the sticky header, not behind it. */
.ev-scope .ev-gen-text :is(h1, h2, h3, h4) { scroll-margin-top: 90px; }
/* Two columns only when there is room for both; below that the aside simply follows the answer, as it did. */
@media (min-width: 1100px) {
  .ev-scope .ev-gen-co { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 24px; align-items: start; }
  .ev-scope .ev-gen-aside { position: sticky; top: 84px; display: flex; flex-direction: column; gap: 14px; max-height: calc(100vh - 104px); overflow-y: auto; }
  .ev-scope .ev-gen-outline-desk { display: block; }
  .ev-scope .ev-gen-outline-phone { display: none; }
}

/* The same generation, published: the reading column, with the map of the answer and the invitation beside it. */
.ev-scope .ev-share-cta { display: flex; flex-direction: column; gap: 14px; border-color: var(--ev-border-default); background: linear-gradient(180deg, var(--ev-surface-card), var(--ev-ink-50)); }
.ev-scope .ev-share-cta h2 { font: 700 19px/1.25 var(--ev-font-display); color: var(--ev-text-strong); }
.ev-scope .ev-share-cta .ev-row { gap: 10px; }
/* The link itself, in the dialog that offers it: readable, selectable, and never pushing the dialog wider. */
.ev-scope .ev-share-link { display: block; width: 100%; font: 500 13px/1.5 var(--ev-font-mono); color: var(--ev-text-strong); background: var(--ev-ink-50); border: 1px solid var(--ev-border-subtle); border-radius: var(--ev-radius-sm); padding: 10px 12px; overflow-wrap: anywhere; }
/* A brand's own mark on the button that sends people to it (WhatsApp). The icon sprite is generated from the
   design prototype and carries only Lucide outlines, no logos, so these come from Font Awesome, which both
   shells already load. Sized here because a glyph follows font-size, and the button's text size would leave the
   logo visibly smaller than the sprite icons it sits beside. */
.ev-scope .ev-btn > .fa-brands { font-size: 17px; line-height: 1; }
.ev-scope .ev-btn.ev-sm > .fa-brands { font-size: 15px; }
.ev-scope .ev-btn.ev-lg > .fa-brands { font-size: 20px; }

/* The content disclaimer that closes the student home. A standing legal notice is equally true on every
   visit, so it is drawn as a footnote under a rule rather than as one more coloured box: an alert that
   never changes stops being read, and this one has to still be there the day it matters. */
.ev-scope .ev-legal { display: flex; align-items: flex-start; gap: 10px; margin: 2px 0 0; padding-top: 16px; border-top: 1px solid var(--ev-border-subtle); font: 400 12.5px/1.75 var(--ev-font-body); color: var(--ev-text-muted); }
.ev-scope .ev-legal .ev-ic { flex: none; margin-top: 4px; color: var(--ev-ink-300); }

/* The generated explanation is longer than a chat reply, so it breathes a little more than .ev-rich does. */
/* Centred, not left-aligned: a capped column inside a wider card hugs the start edge, which on an Arabic
   interface means the English answer sits against the right side of the card with a gutter beside it.
   width:100% is load-bearing. The card is a column flex container, and an auto inline margin takes the item out
   of "stretch" and sizes it to its CONTENT — which on a phone is a 682px answer inside a 375px screen, spilling
   out of the card. The same trap as the capped video player. */
.ev-scope .ev-gen-text { line-height: 1.75; width: 100%; max-width: 88ch; margin-inline: auto; }
/* A player as wide as the screen is nobody's idea of a video. The width is explicit because the card is a column
   flex container: an auto inline margin alone would shrink the box to its content, and the player inside it to 0. */
.ev-scope .ev-gen-video { width: 100%; max-width: 760px; margin-inline: auto; }
.ev-scope .ev-gen-text p { margin: 0 0 1em; }
.ev-scope .ev-gen-text p:last-child { margin-bottom: 0; }
.ev-scope .ev-gen-video { min-height: 140px; display: flex; align-items: center; justify-content: center; }
.ev-scope .ev-gen-video:has(> :not(#evVideoState)) { display: block; }

/* ── AI chat (Pages/Students/AIChat) — hand-written glue for the design's ChatGPT-style chat ───────────────────────
   The prototype's chat fills its #device frame; here the document scrolls, so the chat gets a viewport-based height
   (the empty state centres, the composer sits at the bottom) and the sticky composer clears the phone's tab bar. */
.ev-scope .ev-gpt { min-height: calc(100vh - 290px); }
@media (max-width: 899px) {
  .ev-scope .ev-gcomposer { bottom: calc(60px + env(safe-area-inset-bottom, 0px)); }
}
/* Above the input: the Text / Video answer switch (Shater AI, priced) and the balance chip — advisory, the send re-checks. */
.ev-scope .ev-chat-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.ev-scope .ev-chat-meta:empty { display: none; }
.ev-scope .ev-chat-mode { display: inline-flex; padding: 2px; gap: 2px; background: var(--ev-surface-track); border-radius: 999px; }
.ev-scope .ev-chat-mode button { border: 0; background: transparent; min-height: 30px; padding: 4px 12px; border-radius: 999px; font: 600 12.5px/1.2 var(--ev-font-body); color: var(--ev-ink-600); display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }
.ev-scope .ev-chat-mode button[aria-pressed=true] { background: var(--ev-cobalt-50); color: var(--ev-cobalt-700); box-shadow: var(--ev-shadow-xs); }
.ev-scope .ev-chat-mode button:disabled { opacity: .45; cursor: default; }
.ev-scope .ev-chat-credits { margin-inline-start: auto; font: 600 12.5px/1.3 var(--ev-font-body); color: var(--ev-text-muted); background: var(--ev-surface-card); border: 1px solid var(--ev-border-subtle); border-radius: 999px; padding: 4px 11px; }
.ev-scope .ev-chat-credits.ev-low { background: var(--ev-sun-50); color: var(--ev-sun-700); border-color: var(--ev-sun-100); }
/* The image picked for the next question, shown above the input until it is sent or removed. */
.ev-scope .ev-chat-chip { display: inline-flex; align-items: center; gap: 8px; align-self: flex-start; max-width: 100%; padding: 4px 6px 4px 4px; border-radius: 12px; background: var(--ev-surface-card); border: 1px solid var(--ev-border-default); font-size: 13px; }
.ev-scope .ev-chat-chip img { width: 36px; height: 36px; object-fit: cover; border-radius: 8px; flex: none; }
.ev-scope .ev-chat-chip .ev-grow { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ev-scope .ev-ginput .ev-iconbtn { flex: none; align-self: center; margin-inline-start: -8px; }
.ev-scope .ev-gsend:disabled { opacity: .6; cursor: default; }
/* Inside a message: the student's image, a failed answer, the video answer's slot, scrolling maths. */
.ev-scope .ev-chat-img { display: block; max-width: min(260px, 100%); max-height: 260px; border-radius: 12px; margin-bottom: 6px; }
.ev-scope .ev-gmsg .ev-chat-err { color: var(--ev-ember-700); display: inline-flex; gap: 6px; align-items: center; }
.ev-scope .ev-gmsg .ev-chat-video { display: block; width: 100%; margin-top: 12px; }
.ev-scope .ev-gmsg .ev-rich ul, .ev-scope .ev-gmsg .ev-rich ol { padding-inline-start: 22px; margin: 0 0 10px; }
.ev-scope .ev-gmsg .ev-rich pre { overflow-x: auto; background: var(--ev-ink-50); border-radius: 8px; padding: 10px 12px; }

/* ── Full-width desktop (user decision 2026-10-01) ───────────────────────────────────────────────────────────────
   The prototype draws the portal in a 1160px band (.ev-wrap / the layout container). On a desktop the student
   shell uses the whole screen instead: the header, the review banner, the footer and the page share one gutter,
   wider on big screens. Card grids add columns rather than stretching cards; the two reading surfaces keep a
   readable line — the chat conversation stays a centred column, lesson text is capped at a paragraph width. */
@media (min-width: 900px) {
  .octo-portal-container { max-width: none; }
  body.octo-portal-body .ev-wrap { max-width: none; }
  .ev-scope .ev-cgrid, .ev-scope .ev-home2.ev-solo .ev-cgrid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); }
  .ev-scope .ev-tiles { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
  .ev-scope .ev-gmain > .ev-gtop, .ev-scope .ev-gmain > .ev-gmsgs, .ev-scope .ev-gmain > .ev-gcomposer { width: 100%; max-width: 960px; margin-inline: auto; }
  .ev-scope .ev-lv .ev-rich { max-width: 88ch; }
}
@media (min-width: 1400px) {
  body.octo-portal-body { --ev-gut: 32px; }
}

/* ── Segmented control with links ────────────────────────────────────────────────────────────────────────────────
   The prototype's seg holds buttons only (in-app routing); here a mode that leads to another page is a real <a>
   (Pages/Students/_AISwitch), so the generated button rules are mirrored for links — same size, centring and
   flex share, the pressed look on aria-pressed. */
.ev-scope .ev-seg a { border: 0; background: transparent; min-height: 38px; padding: 6px 14px; border-radius: 8px; font: 600 13.5px/1.2 var(--ev-font-body); color: var(--ev-ink-600); display: inline-flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap; text-decoration: none; }
.ev-scope .ev-seg a:hover { color: var(--ev-text-strong); }
.ev-scope .ev-seg a[aria-pressed=true] { background: var(--ev-cobalt-50); color: var(--ev-cobalt-700); box-shadow: var(--ev-shadow-xs); }
.ev-scope .ev-seg.ev-block a { flex: 1; }
.ev-scope .ev-aiseg a { min-height: 54px; padding: 6px 4px; min-width: 0; flex-direction: column; gap: 3px; font-size: 12.5px; white-space: normal; line-height: 1.15; position: relative; }
@media (min-width: 900px) {
  .ev-scope .ev-aiseg a { flex-direction: row; gap: 8px; min-height: 44px; font-size: 13.5px; }
}

/* ── Full-width desktop, public shell (user decision 2026-10-01) ─────────────────────────────────────────────────
   The landing page, Privacy and the other anonymous pages drop the 1160px band too: every section's content spans
   the screen with the same gutter as the portal. What keeps a measure on a wide screen: the hero's phone stage
   stays a centred column so its floating cards stay beside the phone; the offer cards add columns rather than
   stretching; prose paragraphs keep a paragraph width (the lead and the section heads already do). */
@media (min-width: 900px) {
  body.octo-public-body .ev-wrap { max-width: none; }
  body.octo-public-body .octo-public-container { max-width: none; }
  body.octo-public-body .ev-ld-stage { max-width: 640px; margin-inline: auto; width: 100%; }
  body.octo-public-body .ev-ocards { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); }
  body.octo-public-body .ev-ld-text p, body.octo-public-body .ev-ld-featbody p { max-width: 60ch; }
}
@media (min-width: 1400px) {
  body.octo-public-body { --ev-gut: 32px; }
}

/* ── Offer card: already subscribed ──────────────────────────────────────────────────────────────────────────
   Students were ordering a package they already held (user report, 2026-10-03). Multi-subscription stays on, so
   the card is NOT hidden: it is covered, it names the subscription that already covers them, and ordering again
   takes a second deliberate click (ev-portal.js, data-ev-act="offerReorder", which drops .ev-held). */
.ev-scope .ev-ocard { position: relative; }
.ev-scope .ev-ocard.ev-held > :not(.ev-ohold) { opacity: .3; filter: grayscale(1); pointer-events: none; }
.ev-scope .ev-ocard:not(.ev-held) .ev-ohold { display: none; }
.ev-scope .ev-ohold {
  position: absolute; inset: 0; z-index: 1; border-radius: var(--ev-radius-lg); padding: 18px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 9px; text-align: center;
  background: color-mix(in srgb, var(--ev-surface-card) 88%, transparent);
  backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px);
}
.ev-scope .ev-ohold .ev-bub { flex: none; width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; background: var(--ev-meadow-100); color: var(--ev-meadow-700); }
.ev-scope .ev-ohold strong { font: 600 15px/1.35 var(--ev-font-display); color: var(--ev-text-strong); }
.ev-scope .ev-ohold p { margin: 0; max-width: 32ch; }
.ev-scope .ev-ohold-pkgs { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; max-width: 100%; }
.ev-scope .ev-ohold-pkg { display: inline-flex; align-items: center; padding: 3px 11px; border-radius: var(--ev-radius-pill); background: var(--ev-meadow-50); border: 1px solid var(--ev-meadow-100); color: var(--ev-meadow-700); font: 600 12.5px/1.45 var(--ev-font-body); }

/* ── Home: a subscriber's courses grouped by the package that opens them (user request, 2026-10-03) ──────────
   One block per package: a quiet header naming it and saying when it runs out, then that package's course grid. */
.ev-scope .ev-pkgrp { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.ev-scope .ev-pkgrp-head { display: flex; align-items: center; gap: 9px; padding-bottom: 8px; border-bottom: 1px solid var(--ev-border-subtle); font: 600 14.5px/1.35 var(--ev-font-body); color: var(--ev-text-strong); }
.ev-scope .ev-pkgrp-ic { flex: none; width: 28px; height: 28px; border-radius: var(--ev-radius-sm); display: grid; place-items: center; background: var(--ev-cobalt-50); color: var(--ev-cobalt-700); }
.ev-scope .ev-pkgrp-until { flex: none; font: 500 12.5px/1.4 var(--ev-font-body); color: var(--ev-text-muted); white-space: nowrap; }

/* ── The sign-in card's second way in ──────────────────────────────────────────────────────
   Creating an account used to be a link inside a sentence under the form. Students arriving for
   the first time did not see it. It is now a labelled rule followed by a button the same size and
   shape as Sign in, in the secondary style so the primary action still leads. */
.ev-altsep { display: flex; align-items: center; gap: 12px; margin: 18px 0 12px; color: var(--ev-text-muted); font: 500 12.5px var(--ev-font-body); }
.ev-altsep::before, .ev-altsep::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--ev-border-default); }
.ev-altbtn { display: flex; width: 100%; }

/* ── Checkout: "What's included" rows, and the school's payment illustration ─────────────────────────────────
   A package with no description is a plain row inside the same accordion list, so it still says what the offer
   opens without a toggle that reveals nothing (2026-10-03). */
.ev-scope .ev-acc.ev-plain { display: flex; align-items: center; gap: 10px; min-height: 50px; padding: 8px 12px; font: 600 14.5px/1.35 var(--ev-font-body); color: var(--ev-text-strong); }
.ev-scope .ev-acc.ev-plain .ev-ic { color: var(--ev-meadow-500); flex: none; }
/* The "how to pay" note carries whatever the school pasted. A scanned receipt is an illustration, not the page:
   capped so the form stays the point, and the browser's own zoom still reads the fine print. Block, because an
   inline image this tall drags the heading beside it down onto its own baseline. */
.ev-scope .ev-note .ev-rich img { display: block; max-height: 300px; width: auto; margin: 8px 0; border-radius: var(--ev-radius-sm); }
/* "You choose 1 of these 4 packages" — said above the list, because a bare list of packages reads as a bundle. */
.ev-scope .ev-choosenote { display: flex; align-items: flex-start; gap: 7px; margin: 2px 0 10px; }
.ev-scope .ev-choosenote .ev-ic { flex: none; margin-top: 1px; color: var(--ev-cobalt-600); }

/* ── My devices (Pages/Students/Devices) ─────────────────────────────────────────────────────────────────────
   One row per browser using the account, with the one action that matters: remove it to free a slot. */
.ev-scope .ev-devrow { display: flex; align-items: center; gap: 12px; padding: 12px 14px; background: var(--ev-surface-card); border: 1px solid var(--ev-border-subtle); border-radius: var(--ev-radius-lg); box-shadow: var(--ev-shadow-xs); min-width: 0; }
.ev-scope .ev-devic { flex: none; width: 38px; height: 38px; border-radius: var(--ev-radius-md); display: grid; place-items: center; background: var(--ev-ink-50); color: var(--ev-ink-600); }
.ev-scope .ev-devname { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font: 600 14.5px/1.4 var(--ev-font-body); color: var(--ev-text-strong); min-width: 0; }
.ev-scope .ev-devact { flex: none; margin: 0; }
/* A video the student is not allowed to play right now: the message replaces the player, not the page. */
.ev-scope .ev-playblock { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; text-align: center; padding: 28px 18px; border-radius: var(--ev-radius-lg); background: var(--ev-ink-900); color: var(--ev-ink-200); }
.ev-scope .ev-playblock .ev-ic { color: var(--ev-sun-400); }
.ev-scope .ev-playblock strong { color: #fff; font: 600 15.5px/1.4 var(--ev-font-display); }
.ev-scope .ev-playblock p { margin: 0; max-width: 40ch; font-size: 13.5px; }

/* ── Payment instructions (Configurations → Payment Methods → Tip / Instructions) ──
   The admin writes HTML; these are the few blocks that HTML may use inside the offer page's .ev-rich so the
   steps read as steps and the numbers can be copied: ol.ev-steps, ul.ev-dodont (li.ev-do / li.ev-dont),
   .ev-chips > .ev-chip[data-ev-act="copyText"][data-copy] (tap = copy; the icon is CSS so the HTML stays plain
   enough to survive the Kendo editor), details.ev-shot (a collapsed
   screenshot), p.ev-pay-lead (the "you must pay [total]" line the page puts on top). */
.ev-scope .ev-rich .ev-pay-lead { margin: 2px 0 12px; font-weight: 600; color: var(--ev-text-strong); }
.ev-scope .ev-rich ol.ev-steps { list-style: none; counter-reset: ev-step; margin: 0 0 10px; padding: 0; display: grid; gap: 12px; }
.ev-scope .ev-rich ol.ev-steps > li { counter-increment: ev-step; position: relative; padding-inline-start: 36px; min-height: 26px; }
.ev-scope .ev-rich ol.ev-steps > li::before { content: counter(ev-step); position: absolute; inset-inline-start: 0; top: 1px; width: 26px; height: 26px; border-radius: 50%; background: var(--ev-cobalt-600); color: #fff; font: 700 13px/26px var(--ev-font-display); text-align: center; }
.ev-scope .ev-rich ol.ev-steps > li > p { margin: 6px 0 0; }
.ev-scope .ev-rich ul.ev-dodont { list-style: none; margin: 0 0 10px; padding: 0; display: grid; gap: 8px; }
.ev-scope .ev-rich ul.ev-dodont > li { position: relative; padding-inline-start: 28px; }
.ev-scope .ev-rich ul.ev-dodont > li::before { position: absolute; inset-inline-start: 2px; top: 0; font-weight: 700; }
.ev-scope .ev-rich ul.ev-dodont > li.ev-do::before { content: "\2714"; color: var(--ev-meadow-700); }
.ev-scope .ev-rich ul.ev-dodont > li.ev-dont::before { content: "\2716"; color: var(--ev-ember-600); }
.ev-scope .ev-rich .ev-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
.ev-scope .ev-rich .ev-chip { display: inline-flex; align-items: center; gap: 8px; padding: 6px 8px 6px 12px; border-radius: 999px; background: var(--ev-surface-card); border: 1px solid var(--ev-border-default); color: var(--ev-text-strong); font-weight: 600; text-decoration: none; }
.ev-scope .ev-rich .ev-chip.ev-ltr { direction: ltr; unicode-bidi: isolate; }
.ev-scope .ev-rich .ev-chip[data-ev-act] { cursor: pointer; }
/* The glyph is drawn by the brand (--ev-glyph-copy, BrandTokenDeriver) in its own ink, light and dark; the
   platform's blue is only the fallback. Hover changes the colour alone: the shorthand would drop the glyph. */
.ev-scope .ev-rich .ev-chip[data-ev-act]::after { content: ""; width: 26px; height: 26px; border-radius: 50%; background: var(--ev-cobalt-50) var(--ev-glyph-copy, url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%231E38BE' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='9' y='9' width='12' height='12' rx='2'/%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'/%3E%3C/svg%3E")) center / 14px no-repeat; }
.ev-scope .ev-rich .ev-chip[data-ev-act]:hover::after { background-color: var(--ev-cobalt-100, #dde3fb); }
.ev-scope .ev-rich .ev-warnline { margin: 6px 0 0; color: var(--ev-ember-700); font-weight: 600; }
.ev-scope .ev-rich details.ev-shot { margin: 8px 0 4px; border: 1px solid var(--ev-border-subtle); border-radius: var(--ev-radius-sm); background: var(--ev-surface-card); }
.ev-scope .ev-rich details.ev-shot > summary { cursor: pointer; padding: 10px 12px; font-weight: 600; color: var(--ev-cobalt-700); list-style: none; display: flex; align-items: center; gap: 8px; }
.ev-scope .ev-rich details.ev-shot > summary::-webkit-details-marker { display: none; }
.ev-scope .ev-rich details.ev-shot > summary::before { content: "\25B8"; font-size: 12px; transition: transform .15s; }
.ev-scope .ev-rich details.ev-shot[open] > summary::before { transform: rotate(90deg); }
[dir="rtl"] .ev-scope .ev-rich details.ev-shot > summary::before { content: "\25C2"; }
[dir="rtl"] .ev-scope .ev-rich details.ev-shot[open] > summary::before { transform: rotate(-90deg); }
.ev-scope .ev-rich details.ev-shot img { display: block; margin: 0 auto 12px; width: auto; max-width: calc(100% - 24px); max-height: 380px; border-radius: var(--ev-radius-sm); border: 1px solid var(--ev-border-subtle); }
.ev-scope .ev-rich details.ev-shot img + img { margin-top: 10px; }
@media (max-width: 600px) { .ev-scope .ev-rich details.ev-shot img { max-height: 60vh; } }

/* ── Student Home: the free preview's strip and the class cards (2026-10-07) ── */
/* The strip takes the subscriber strip's shape in the preview's cobalt; the way out (Subscribe, Activate a key)
   sits at its end and drops to its own row on a phone. */
.ev-substrip.ev-free { background: var(--ev-cobalt-50); border-color: var(--ev-cobalt-200); flex-wrap: wrap; cursor: default; }
.ev-substrip.ev-free:hover { background: var(--ev-cobalt-50); }
.ev-substrip.ev-free .ev-bub { background: var(--ev-cobalt-600); }
.ev-substrip.ev-free strong { color: var(--ev-cobalt-700); }
.ev-substrip.ev-free .ev-l2 a { color: var(--ev-cobalt-700); font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
.ev-substrip.ev-free .ev-acts { display: flex; gap: 8px; flex-wrap: wrap; margin-inline-start: auto; }
@media (max-width: 559px) { .ev-substrip.ev-free .ev-acts { width: 100%; margin-inline-start: 48px; } }
/* "Which class are you in?": a card per package with the course count beneath the name, two across on a phone and
   four on a desktop; the dialog shows the same choice as the .ev-pkg rows the lesson-language dialog uses. */
.ev-classask { display: flex; flex-direction: column; gap: 12px; }
.ev-classask h3 { margin: 0; font: 700 18px/1.3 var(--ev-font-display); color: var(--ev-text-strong); }
.ev-classask p { margin: 0; }
.ev-classes { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.ev-classes.ev-list { grid-template-columns: minmax(0, 1fr); gap: 8px; }
@media (min-width: 900px) { .ev-classes:not(.ev-list) { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.ev-class { display: flex; flex-direction: column; align-items: stretch; gap: 14px; min-height: 124px; padding: 16px; text-align: start; background: var(--ev-surface-card); border: 1.5px solid var(--ev-border-default); border-radius: var(--ev-radius-lg); box-shadow: var(--ev-shadow-xs); color: inherit; font: inherit; cursor: pointer; transition: box-shadow .15s, border-color .15s; }
.ev-class:hover { box-shadow: var(--ev-shadow-md); border-color: var(--ev-cobalt-600); }
.ev-class[aria-pressed="true"] { border-color: var(--ev-cobalt-600); background: var(--ev-cobalt-50); }
.ev-class-meta { margin-top: auto; display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--ev-text-muted); }
.ev-class-meta .ev-ic { margin-inline-start: auto; color: var(--ev-cobalt-600); transition: transform .15s; }
.ev-class:hover .ev-class-meta .ev-ic { transform: translateX(3px); }
[dir="rtl"] .ev-class:hover .ev-class-meta .ev-ic { transform: translateX(-3px); }
.ev-pkg .ev-tt-p { font-size: 15px; }

/* ── Checkout: the QR code a Whish to Whish payment is scanned from (2026-10-10) ── */
.ev-scope .ev-rich .ev-qr { display: flex; flex-direction: column; align-items: center; gap: 6px; margin: 4px 0 14px; }
.ev-scope .ev-rich .ev-qr img { width: 220px; height: 220px; padding: 6px; background: #fff; border: 1px solid var(--ev-border-subtle); border-radius: var(--ev-radius-md); }

/* Order Done, a paid Whish Pay order: the activation key(s), big enough to read out and one tap to copy. */
.ev-scope .ev-keyrow{display:flex;align-items:center;gap:10px;justify-content:center}
.ev-scope .ev-keycode{font:600 20px/1.2 var(--ev-font-mono);letter-spacing:.12em;color:var(--ev-text-strong);background:var(--ev-ink-100);border-radius:10px;padding:10px 16px;user-select:all}
