/* eye-training.css
   Full-bleed layout for the ガボール・アイ exercise inside eye-training/.
   Loaded AFTER css/app.css (see eye-training/index.html <link> order), so
   any rule here beats an equal-specificity rule in app.css -- this file
   intentionally overrides a few app.css selectors (.gabor-area,
   .gabor-target-wrap, .gabor-grid, .gabor-cell) instead of editing app.css,
   per this feature's file-ownership rules.

   Design brief (client): the client wants the "search among these" grid to
   fill nearly the whole screen while doing the exercise, like the double-
   page spread in the source book, because he visually scans the whole grid
   at once. So while a ガボール・アイ round is running we:
     - hide chrome (top bar, headings, setup card, results card, footer)
     - show only a compact "target patch + goal count + progress" header,
       a small stop button + timer ring, and the grid -- sized in JS
       (see js/gabor-layout.js) to exactly fill the remaining viewport with
       no vertical scrolling.

   The "target patch always visible" requirement (#2 in the brief) is met
   structurally, not by CSS position:sticky: .gabor-sticky-header is a
   sibling BEFORE .gabor-grid-scroll inside a flex column, and only
   .gabor-grid-scroll scrolls (overflow:auto, used only as a safety-net
   fallback for pathological viewport/zoom combinations -- normal operation
   never needs it because the grid is sized to fit). A header outside the
   scrolling region can never be scrolled out of view, which is a stronger
   guarantee than `position: sticky` gives.
*/

/* -------------------- fullscreen chrome hiding -------------------- */

body.gabor-fullscreen {
  overflow: hidden;
}

body.gabor-fullscreen .top-bar,
body.gabor-fullscreen h1,
body.gabor-fullscreen .lead,
body.gabor-fullscreen .notice-warn,
body.gabor-fullscreen .audio-widget,
body.gabor-fullscreen #setup-card,
body.gabor-fullscreen #results-card,
body.gabor-fullscreen .feedback-footer {
  display: none;
}

/* -------------------- the full-bleed training card -------------------- */

body.gabor-fullscreen #training-card {
  position: fixed;
  inset: 0;
  z-index: 100;
  margin: 0;
  border: none;
  border-radius: 0;
  box-shadow: none;
  background: var(--page);
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto 1fr;
  grid-template-areas:
    "stop   status ring"
    "gabor  gabor  gabor";
  gap: 2px 10px;
  /* 100vh first as a fallback for browsers without dvh support (iOS
     Safari's address bar shifts 100vh out from under fixed elements --
     100dvh tracks the actual visible viewport). */
  height: 100vh;
  height: 100dvh;
  padding: max(8px, env(safe-area-inset-top)) max(8px, env(safe-area-inset-right))
    max(8px, env(safe-area-inset-bottom)) max(8px, env(safe-area-inset-left));
}

/* The other 3 exercises' stage never runs in fullscreen (only ガボール・
   アイ's onEnter/onExit toggle body.gabor-fullscreen), but stay defensive:
   an un-placed grid item would otherwise get auto-placed into a stray
   implicit track and skew the layout. */
body.gabor-fullscreen #training-card > .stage {
  display: none;
}

body.gabor-fullscreen #start-stop-btn {
  grid-area: stop;
  align-self: center;
  width: auto;
  min-height: 44px;
  padding: 6px 16px;
  font-size: 0.95rem;
  border-radius: var(--radius-sm);
}

body.gabor-fullscreen #status-text {
  grid-area: status;
  align-self: center;
  margin: 0;
  font-size: 0.95rem;
  line-height: 1.3;
  text-align: left;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

body.gabor-fullscreen .progress-ring-wrap {
  grid-area: ring;
  align-self: center;
  margin: 0;
}

body.gabor-fullscreen .progress-ring-wrap svg {
  width: 48px;
  height: 48px;
}

body.gabor-fullscreen .progress-ring-wrap .ring-time {
  font-size: 0.68rem;
}

body.gabor-fullscreen .gabor-area {
  grid-area: gabor;
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
}

body.gabor-fullscreen #gabor-best-line {
  display: none;
}

/* -------------------- inline difficulty/time controls -------------------- */
/* #gabor-inline-controls carries `hidden`, toggled in lockstep with
   body.gabor-fullscreen by moveGaborControlsInline/restoreGaborControlsHome
   in eye-training/app.js -- scoped under body.gabor-fullscreen for the same
   [hidden]-defeat reason documented above for .gabor-sticky-header. It
   holds the SAME #difficulty-row/#duration-row elements normally shown in
   #setup-card (relocated, not duplicated) so かんたん/ふつう/むずかしい and
   the time presets stay reachable while #setup-card itself is display:none. */
body.gabor-fullscreen .gabor-inline-controls {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: none;
  padding: 4px 2px;
  border-bottom: 1px solid var(--border);
}

.gabor-inline-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.gabor-inline-label {
  flex: none;
  font-size: 0.8rem;
  color: var(--ink-muted);
  min-width: 3em;
}

.gabor-inline-slot {
  flex: 1 1 auto;
  min-width: 0;
}

/* Compact variant of .option-row/button (css/app.css) -- keeps a real tap
   target (44px, same floor as #start-stop-btn above) while leaving most of
   the screen to the grid. */
.gabor-inline-slot .option-row {
  margin-bottom: 0;
  gap: 6px;
  flex-wrap: nowrap;
}

.gabor-inline-slot .option-row button {
  min-height: 44px;
  min-width: 0;
  padding: 4px 8px;
  font-size: 0.85rem;
  border-radius: var(--radius-sm);
}

/* -------------------- sticky-by-structure target header -------------------- */
/* #gabor-sticky-header and #gabor-grid-scroll both carry the HTML `hidden`
   attribute, toggled in lockstep with body.gabor-fullscreen by
   eye-training/app.js (EXERCISES.gabor.onEnter/onExit). Their `display`
   overrides below are deliberately scoped under body.gabor-fullscreen too
   -- an unscoped `display: flex` here would out-cascade the UA stylesheet's
   `[hidden] { display: none }` (author rules beat UA rules at equal
   specificity) and defeat `hidden` while the exercise isn't running. */

body.gabor-fullscreen .gabor-sticky-header {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: none;
  padding: 2px 2px 8px;
}

.gabor-target-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  flex: none;
}

.gabor-target-wrap canvas {
  display: block;
  /* ~1.5-2x the old fixed 72px, scaled down on narrow phones via vw so the
     sticky header (sample + goal text, side by side) never forces a
     horizontal scroll. eye-training/app.js reads the resulting CSS box
     size (getBoundingClientRect) and sets the canvas's backing-store pixel
     size to that times devicePixelRatio, so this is never a blurry
     upscaled bitmap -- see renderGaborTargetCanvas(). */
  width: clamp(84px, 26vw, 140px);
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: #808080;
}

.gabor-target-label {
  font-size: 0.8rem;
  color: var(--ink-muted);
}

.gabor-goal-wrap {
  flex: 1 1 auto;
  min-width: 0;
}

.gabor-goal-text {
  margin: 0 0 2px;
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.3;
}

.gabor-goal-text strong {
  color: var(--accent);
  font-size: 1.3em;
}

.gabor-progress-text {
  margin: 0;
  font-size: 1rem;
  color: var(--ink-2);
}

.gabor-session-line {
  margin: 2px 0 0;
  font-size: 0.82rem;
  color: var(--ink-muted);
}

/* -------------------- the grid itself -------------------- */

/* Also carries `hidden` -- scoped under body.gabor-fullscreen for the same
   reason as .gabor-sticky-header above. */
body.gabor-fullscreen .gabor-grid-scroll {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Safety-net fallback only -- see file header comment. Under normal
     operation the JS-computed grid always fits without scrolling. */
  overflow: auto;
  -webkit-overflow-scrolling: touch;
}

/* ONE continuous flat play field -- #808080 is the exact mid-gray
   js/gabor.js (computeGaborGrayscale, mid = 128) fills outside each
   patch's Gaussian envelope, so a patch's square canvas edge is invisible
   and only its stripes show; there is no per-patch tile/border/gap left to
   give away a lattice. eye-training/app.js absolutely-positions each
   .gabor-cell at a coordinate computed by js/gabor-layout.js (which
   guarantees no two patches overlap) instead of laying them out in a CSS grid --
   position: relative here only establishes the containing block for those
   absolutely-positioned children. */
/* display/gap/margin-bottom reset: css/app.css's base .gabor-grid rule
   (a flex-wrap lattice container for the non-fullscreen page background --
   out of scope here) still applies at equal specificity to anything this
   rule doesn't set, since this file only overrides the selectors it names
   (see file header). */
.gabor-grid {
  display: block;
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0;
  background: #808080;
}

.gabor-cell {
  position: absolute;
  padding: 0;
  margin: 0;
  min-width: 0;
  min-height: 0;
  border: none;
  border-radius: 0;
  background: none;
  line-height: 0;
  /* app.css's base .gabor-cell sets overflow: hidden (clips a tile's own
     canvas) -- reset to visible here so a found/flash ring's box-shadow,
     which paints outside this element's own border box, is never clipped
     by its own overflow. */
  overflow: visible;
}

.gabor-cell canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.gabor-cell:disabled {
  cursor: default;
  opacity: 1;
}

/* Tap feedback. The flat field has no tile styling left to repurpose, so a
   tap gets a brief expanding ring instead (both outcomes), and a correct
   find keeps a persistent thin ring afterwards so the player can see which
   patches are already found. A wrong tap deliberately gets no persistent
   marker -- a permanently visible "already tried, wrong" ring would give
   away part of the answer and work against "hard to find" (see
   eye-training/app.js handleGaborTap). Both animations resolve to a fully
   solid/transparent box-shadow so a reduced-motion collapse (see
   css/app.css's prefers-reduced-motion / .reduce-motion rules, which force
   animation-duration to ~0) still lands on the correct end state instead of
   a half-animated one. */
.gabor-cell.correct {
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--success);
}

@keyframes gabor-flash-correct {
  from { box-shadow: 0 0 0 12px var(--success-bg), 0 0 0 2px var(--success); }
  to { box-shadow: 0 0 0 2px var(--success); }
}

@keyframes gabor-flash-wrong {
  from { box-shadow: 0 0 0 12px var(--danger-bg), 0 0 0 2px var(--danger); }
  to { box-shadow: 0 0 0 0 transparent; }
}

.gabor-cell.flash-correct {
  border-radius: 50%;
  animation: gabor-flash-correct 550ms ease-out;
}

.gabor-cell.flash-wrong {
  border-radius: 50%;
  animation: gabor-flash-wrong 550ms ease-out;
}

.gabor-pattern-details { margin: 0 0 24px; border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px; }
.gabor-pattern-details summary { cursor: pointer; min-height: 44px; padding: 8px 0; font-weight: 700; }
.gabor-layout-picker { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 10px; max-height: 370px; overflow-y: auto; padding: 4px; }
.gabor-layout-picker button { min-width: 0; flex-direction: column; padding: 8px; font-size: 1rem; gap: 4px; }
.gabor-layout-picker canvas { width: 100%; height: auto; background: #edf2f6; border-radius: 6px; }
.gabor-layout-picker button[aria-pressed="true"] { border: 3px solid var(--accent); background: var(--surface-2); font-weight: 700; }
.gabor-round-label { font-size: .8rem; line-height: 1.4; color: var(--ink-muted); margin: 0 0 4px; }
@media (max-width: 420px) { .gabor-layout-picker { grid-template-columns: repeat(2,minmax(0,1fr)); } }
.gabor-grid { flex: none; }

/* Compact training toolbar: reference, progress, timer, settings and stop in
   one row. Settings float over the board only when explicitly opened. */
body.gabor-fullscreen #training-card {
  grid-template-columns: minmax(0,1fr) 52px auto;
  grid-template-rows: auto minmax(0,1fr) auto;
  grid-template-areas: "sample settings stop" "board board board" "actions actions actions";
  gap: 4px;
}
body.gabor-fullscreen .gabor-area { display: contents; }
body.gabor-fullscreen .gabor-sticky-header { grid-area: sample; padding: 0; gap: 8px; min-width: 0; }
body.gabor-fullscreen .gabor-target-wrap { position: relative; margin: 0; }
body.gabor-fullscreen .gabor-target-wrap canvas { width: 96px; height: 96px; }
body.gabor-fullscreen .gabor-target-label { position: absolute; bottom: 0; background: var(--surface); padding: 0 6px; border-radius: 4px; font-size: .7rem; }
body.gabor-fullscreen .gabor-progress-text { font-size: 1rem; font-weight: 700; }
body.gabor-fullscreen .gabor-round-label { font-size: .75rem; }
body.gabor-fullscreen #status-text,
body.gabor-fullscreen #gabor-goal-text,
body.gabor-fullscreen #gabor-session-line {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; margin: 0; padding: 0;
}
body.gabor-fullscreen .progress-ring-wrap { grid-area: settings; align-self: start; }
body.gabor-fullscreen .progress-ring-wrap svg { width: 42px; height: 42px; }
body.gabor-fullscreen #start-stop-btn { padding: 8px; align-self: center; }
body.gabor-fullscreen .gabor-inline-controls { grid-area: settings; align-self: end; position: relative; display: block; padding: 0; border: 0; z-index: 120; }
.gabor-inline-controls > summary { min-height: 44px; display: flex; align-items: center; justify-content: center; cursor: pointer; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); font-size: .9rem; list-style: none; }
.gabor-inline-controls > summary::-webkit-details-marker { display: none; }
.gabor-inline-controls[open] > summary { background: var(--surface-2); border-color: var(--accent); }
.gabor-settings-panel { position: absolute; top: calc(100% + 8px); right: -65px; width: min(350px,calc(100vw - 24px)); padding: 12px; border: 1px solid var(--border); border-radius: 12px; background: var(--surface); box-shadow: var(--shadow); }
body.gabor-fullscreen .gabor-grid-scroll { grid-area: board; }
@media (max-width: 360px) {
  body.gabor-fullscreen .gabor-round-label { display: none; }
  body.gabor-fullscreen .gabor-progress-text { font-size: .85rem; }
}

/* -------------------- 正解 / 次へ action row (2026-09-25 client feedback) -------------------- */
/* Own grid row below the board, never over it -- so these two large buttons
   can never obscure a patch or be hit by an accidental tap meant for the
   board (js/drag-pan.js and its click-suppression were removed in dd1bdfd;
   the board is now always laid out to fit the viewport without scrolling or
   panning, so there is no drag gesture left to protect here). Both buttons
   end the current round in an honest, non-"found" way -- see
   eye-training/app.js revealGaborAnswer/skipGaborRound. */
body.gabor-fullscreen .gabor-action-row {
  grid-area: actions;
  display: flex;
  gap: 8px;
  padding: 2px 2px max(2px, env(safe-area-inset-bottom));
}

.gabor-action-btn {
  flex: 1 1 0;
  min-height: 56px;
  font-size: 1.05rem;
  font-weight: 700;
  border-radius: var(--radius-sm);
}

@media (min-width: 700px) {
  body.gabor-fullscreen .gabor-action-row { justify-content: center; }
  .gabor-action-btn { flex: 0 0 260px; min-height: 64px; font-size: 1.15rem; }
}

/* Reveal highlight -- deliberately a third color (amber/--warn), distinct
   from the green "found" ring (.gabor-cell.correct) and the brief red
   "wrong" flash, so a revealed patch reads unambiguously as "shown to you",
   not "you got it". Pulses twice so it stays noticeable across a glance at
   a large external monitor; prefers-reduced-motion collapses this like every
   other animation in the app (css/app.css). */
.gabor-cell.revealed {
  border-radius: 50%;
  box-shadow: 0 0 0 3px var(--warn);
  animation: gabor-reveal-pulse 700ms ease-in-out 2;
}

@keyframes gabor-reveal-pulse {
  0%, 100% { box-shadow: 0 0 0 3px var(--warn); }
  50% { box-shadow: 0 0 0 14px var(--warn-bg), 0 0 0 3px var(--warn); }
}

/* -------------------- pause overlay (2026-09-25 client feedback) -------------------- */
/* Sits OUTSIDE #training-card (see eye-training/index.html) so it is never
   itself made inert when #training-card is; a translucent scrim (not an
   opaque one) keeps the paused board visible underneath, reinforcing that
   pausing keeps the session rather than discarding it. */
.gabor-pause-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(16, 16, 16, 0.6);
}

.gabor-pause-panel {
  width: min(360px, 100%);
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 24px;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
  text-align: center;
}

.gabor-pause-title {
  margin: 0;
  font-size: 1.4rem;
  font-weight: 700;
}

.gabor-pause-hint {
  margin: 0;
  font-size: 0.85rem;
}
