/* ==========================================================================
   MA (間) — Zen Ink-Wash entry portal
   Sumi-e on washi. Immense negative space. The type is a whisper; the ink
   is the voice. Everything here is quiet by design.
   ========================================================================== */

:root{
  /* washi paper — warm, fibrous off-white */
  --washi:      #f1ebdd;
  --washi-deep: #e8e0cd;   /* soft shadow of the paper */
  /* sumi ink — warm charcoal, never pure black */
  --sumi:       #211d18;
  --sumi-soft:  #453e33;   /* readable secondary */
  --sumi-faint: #8c8371;   /* whisper labels */
  /* shu — the vermilion of a carved seal, used once */
  --shu:        #b5432b;
  --shu-ink:    #faf5ea;

  --serif: "Yu Mincho", "Hiragino Mincho ProN", "Songti SC", "Noto Serif JP",
           "Sorts Mill Goutte", Georgia, "Times New Roman", serif;
  --sans:  "Helvetica Neue", "Segoe UI", system-ui, -apple-system, sans-serif;

  --breath: 8.5s;   /* one slow inhale/exhale of the enso */
}

*{ box-sizing:border-box; }

html,body{ height:100%; }

body{
  margin:0;
  min-height:100dvh;
  background:
    radial-gradient(120% 90% at 50% 42%, var(--washi) 55%, var(--washi-deep) 100%);
  color:var(--sumi);
  font-family:var(--serif);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow:hidden;
  position:relative;
}

/* faint fibrous grain, painted as a repeating whisper — pure CSS, no assets */
body::before{
  content:"";
  position:fixed; inset:0; z-index:0; pointer-events:none;
  opacity:.5;
  background-image:
    repeating-linear-gradient(0deg,   rgba(33,29,24,.014) 0 1px, transparent 1px 3px),
    repeating-linear-gradient(90deg,  rgba(33,29,24,.010) 0 1px, transparent 1px 4px);
  mix-blend-mode:multiply;
}

.skip{
  position:absolute; left:-9999px; top:0; z-index:100;
  background:var(--sumi); color:var(--washi);
  padding:.6rem 1rem; border-radius:2px; text-decoration:none;
  font-family:var(--sans); font-size:.8rem; letter-spacing:.06em;
}
.skip:focus{ left:1rem; top:1rem; }

/* -------- the enso canvas: full-bleed, centered, behind the words -------- */
.ink{
  position:fixed; inset:0; z-index:1;
  width:100%; height:100%;
  transform-origin:50% 46%;
  will-change:transform, opacity;
  pointer-events:none;
}
/* the breath — a very slow scale + luminance sway, handled by CSS so the
   canvas is drawn only once. Paused for reduced motion. */
body.settled .ink{ animation:breathe var(--breath) ease-in-out infinite; }
@keyframes breathe{
  0%,100%{ transform:scale(1);      opacity:.94; }
  50%    { transform:scale(1.017);  opacity:1;    }
}

/* -------- brand mark -------- */
.mark{
  position:fixed; z-index:3; top:clamp(1.4rem,4vh,2.6rem); left:clamp(1.4rem,4vw,3rem);
  display:flex; align-items:baseline; gap:.7rem;
}
.mark-glyph{ font-size:1.5rem; color:var(--sumi); line-height:1; }
.mark-name{
  font-family:var(--sans); font-size:.72rem; font-weight:600;
  letter-spacing:.62em; padding-left:.2em; color:var(--sumi-soft);
}

/* -------- the stage: one column, centered in immense space -------- */
.stage{
  position:relative; z-index:2;
  min-height:100dvh;
  display:grid; place-items:center;
  padding:8vh clamp(1.5rem,6vw,4rem);
}
.center{
  position:relative;
  text-align:center;
  max-width:34rem;
  display:flex; flex-direction:column; align-items:center;
}
/* legibility scrim: a soft breath of washi behind the words so the headline and
   subhead never lose contrast where they cross the ink of the enso stroke */
.center::before{
  content:"";
  position:absolute; z-index:-1;
  left:50%; top:42%; transform:translate(-50%,-50%);
  width:min(34rem,92vw); height:160%;
  background:radial-gradient(60% 50% at 50% 42%,
    var(--washi) 0%, rgba(241,235,221,.95) 48%, rgba(241,235,221,0) 84%);
  pointer-events:none;
}

.say{
  margin:0;
  font-weight:400;
  font-size:clamp(1.9rem,4.6vw,3rem);
  letter-spacing:.12em;
  line-height:1.25;
  color:var(--sumi);
  /* the words sit inside the enso's opening */
}
.sub{
  margin:1.5rem 0 0;
  max-width:20rem;
  font-size:clamp(.86rem,1.7vw,1rem);
  line-height:1.85;
  letter-spacing:.02em;
  color:var(--sumi-soft);
}

/* -------- the gate: a single hairline, no box -------- */
.gate-form{
  margin-top:clamp(3rem,9vh,5.5rem);
  width:min(24rem,80vw);
  display:flex; flex-direction:column; align-items:center;
}
.field-label{
  font-family:var(--sans);
  font-size:.66rem; font-weight:600;
  letter-spacing:.42em; text-transform:uppercase;
  color:var(--sumi-faint);
  padding-left:.42em;
}
.field{
  margin-top:1.1rem;
  width:100%;
  display:flex; align-items:center; gap:1rem;
  border-bottom:1px solid rgba(33,29,24,.28);
  transition:border-color .5s ease;
}
.field:focus-within{ border-color:var(--sumi); }

.field-input{
  flex:1 1 auto; min-width:0;
  background:transparent; border:0; outline:0;
  padding:.55rem .2rem .7rem;
  font-family:var(--serif);
  font-size:1.12rem; letter-spacing:.34em; color:var(--sumi);
  caret-color:var(--shu);
}
.field-input::placeholder{ color:var(--sumi-faint); }

/* the seal — a carved vermilion stamp; the only saturated mark on the page */
.seal{
  flex:0 0 auto;
  width:2.6rem; height:2.6rem;
  display:grid; place-items:center;
  background:var(--shu);
  color:var(--shu-ink);
  border:0; border-radius:3px;
  cursor:pointer;
  box-shadow:0 1px 0 rgba(33,29,24,.12);
  transition:transform .35s cubic-bezier(.2,.8,.2,1), background .35s ease, opacity .35s ease;
  -webkit-tap-highlight-color:transparent;
}
.seal-glyph{ font-family:var(--serif); font-size:1.15rem; line-height:1; transform:translateY(.02em); }
.seal:hover{ background:#a53a24; transform:translateY(-1px); }
.seal:active{ transform:translateY(0) scale(.97); }
.seal:disabled{ opacity:.34; cursor:default; transform:none; }

/* verdicts — quiet, appear below the line */
.verdict{
  min-height:1.2em;
  margin:1.5rem 0 0;
  font-family:var(--sans);
  font-size:.78rem; letter-spacing:.08em;
  color:var(--sumi-soft);
  opacity:0; transition:opacity .5s ease;
}
.verdict:not(:empty){ opacity:1; }
.verdict.is-deny{ color:var(--shu); }

/* denied — a short, soft tremor, like a brush lifting */
body.denied .gate-form{ animation:tremor .5s ease; }
@keyframes tremor{
  0%,100%{ transform:translateX(0); }
  20%{ transform:translateX(-5px); }
  40%{ transform:translateX(4px); }
  60%{ transform:translateX(-3px); }
  80%{ transform:translateX(2px); }
}

/* -------- footer whisper -------- */
.foot{
  position:fixed; z-index:3; bottom:clamp(1.3rem,4vh,2.4rem); left:0; right:0;
  display:flex; align-items:center; justify-content:center; gap:1rem;
  font-family:var(--sans); font-size:.62rem; letter-spacing:.42em; text-transform:uppercase;
  color:var(--sumi-faint);
}
.foot > span:first-child{ font-family:var(--serif); letter-spacing:0; font-size:.9rem; }
.foot-line{ width:min(9rem,20vw); height:1px; background:rgba(33,29,24,.22); }

/* -------- focus visibility (keyboard) -------- */
:focus-visible{ outline:2px solid var(--sumi); outline-offset:4px; border-radius:2px; }
.seal:focus-visible{ outline-offset:3px; }
.field-input:focus-visible{ outline:none; }  /* the underline is its focus cue */

/* ==========================================================================
   GESTURE-TECH SHOWCASE — eight quiet demonstrations, each a distinct web
   technology rendered in sumi ink. Ambient, behind the words, never blocking
   the gate. Fades in on gesture, settles back to the enso.
   ========================================================================== */
.fx{
  position:fixed; inset:0; z-index:1;
  width:100%; height:100%;
  pointer-events:none;
  opacity:0; transition:opacity .7s ease;
  will-change:opacity;
}
.fx.on{ opacity:1; }
.fx-svg{ display:block; }

/* the gesture caption — a whisper naming what just fired */
.fxlabel{
  position:fixed; z-index:4;
  top:clamp(4.8rem,13vh,7.4rem); left:0; right:0;
  margin:0; padding:0 1rem; text-align:center;
  font-family:var(--sans);
  font-size:.62rem; font-weight:600; letter-spacing:.34em; text-transform:uppercase;
  color:var(--sumi-soft);
  opacity:0; transition:opacity .5s ease;
  pointer-events:none;
}
.fxlabel.on{ opacity:1; }

/* CSS mask radial reveal (click-hold): an ink wash uncovers the mark through
   an animated radial-gradient mask */
.reveal{
  position:fixed; inset:0; z-index:20;
  display:grid; place-items:center;
  pointer-events:none; opacity:0;
}
.reveal-in{
  display:flex; flex-direction:column; align-items:center; gap:1.1rem;
  color:var(--sumi);
  -webkit-mask-image:radial-gradient(circle at 50% 50%, #000 40%, transparent 62%);
          mask-image:radial-gradient(circle at 50% 50%, #000 40%, transparent 62%);
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center;  mask-position:center;
  -webkit-mask-size:0% 0%;       mask-size:0% 0%;
}
.reveal.on{ opacity:1; }
.reveal.on .reveal-in{ animation:unmask 2.6s cubic-bezier(.2,.7,.2,1) forwards; }
@keyframes unmask{
  0%  { -webkit-mask-size:0% 0%;     mask-size:0% 0%;     opacity:1; }
  62% { -webkit-mask-size:300% 300%; mask-size:300% 300%; opacity:1; }
  100%{ -webkit-mask-size:340% 340%; mask-size:340% 340%; opacity:0; }
}
.reveal-glyph{ font-family:var(--serif); font-size:clamp(4rem,20vw,9rem); line-height:1; }
.reveal-cap{
  font-family:var(--sans); font-size:.66rem; font-weight:600;
  letter-spacing:.4em; text-transform:uppercase; color:var(--sumi-soft);
}

/* -------- the crossing: ink blooms across the washi, then we step through -- */
.wash{
  position:fixed; inset:0; z-index:40; pointer-events:none;
  background:radial-gradient(closest-side at 50% 46%, var(--sumi) 0%, rgba(33,29,24,.9) 60%, rgba(33,29,24,0) 100%);
  opacity:0; transform:scale(.18); transform-origin:50% 46%;
}
body.crossing .wash{
  animation:bloom 1.5s cubic-bezier(.5,.0,.2,1) forwards;
}
body.crossing .center,
body.crossing .mark,
body.crossing .foot{ transition:opacity .6s ease; opacity:0; }
body.crossing .ink{ transition:opacity 1s ease, transform 1.4s ease; opacity:0; transform:scale(1.09); }
@keyframes bloom{
  0%  { opacity:0;   transform:scale(.18); }
  35% { opacity:.4;  }
  100%{ opacity:1;   transform:scale(2.4); }
}

/* -------- reduced motion: everything comes to rest -------- */
@media (prefers-reduced-motion: reduce){
  body.settled .ink{ animation:none; }
  body.denied .gate-form{ animation:none; }
  body.crossing .wash{ animation:none; opacity:1; transform:scale(2.4); transition:opacity .3s ease; }
  .seal{ transition:none; }
  /* the showcase renders a single still frame instead of looping motion */
  .reveal.on .reveal-in{ animation:none; -webkit-mask-size:340% 340%; mask-size:340% 340%; }
  .fx{ transition:opacity .3s ease; }
}

/* -------- narrow screens -------- */
@media (max-width:480px){
  .mark-name{ letter-spacing:.4em; }
  .say{ letter-spacing:.08em; }
  .foot-line{ width:5rem; }
}

/* exit veil: the LAST painted frame before leaving must match the app's dark canvas (#0b0d12) so the
   browser never shows a bright frame between documents — photosensitivity mandate, applies to every exit */
.exit-veil{ position:fixed; inset:0; z-index:2147483000; background:#0b0d12; opacity:0; pointer-events:none; transition:opacity .3s ease; }
.exit-veil.is-on{ opacity:1; }
@media (prefers-reduced-motion: reduce){ .exit-veil{ transition:none; } }


/* =========================================================================================================
   BIND PORT - THE CHIP'S LANE, AND THE KEYBOARD'S OWN VIEWPORT STATE
   PORT-NOTES STEP 7 - R319/§312 SESSION-BIND, piece portals-p4p5p6p7. ASL v2 Laws 1, 3, 4, 5 and 11;
   brief items 6, 26, 27, 28 and 33.

   Everything above this line is the host's: the scene, the type, the copy, the colours, the key form's
   markup. This block is the one thing bind.com puts on the door that the host never had -
   wall/shared/motion.css, which fixes the shared sensor chip to the block-end corner of the VIEWPORT at
   calc(14px + env(safe-area-inset-bottom)). A fixed element does not scroll, so a document that scrolls
   underneath it can never end above it, and the profile ASL Law 11 names - Keyboard-open, 390 x 430 - is
   exactly where a door's key row lands in the corner the chip is fixed in.

   THE LANE is PORT-NOTES STEP 7's own string, and it is a reservation, not a restyle: the chip is never
   moved, shrunk, hidden or dropped to make the room. 52px is the chip's tallest measured box (44px at 390
   and above, 52px at 320 and 344 where its armed line wraps to two); 14px is the inset the chip already
   sits at, and 14px more is the gutter between the two; the safe-area inset is added again because the
   chip's own position carries it. tests/portal-gate.mjs re-measures the real chip at both widths on every
   run, so a chip that ever grows reddens the arm instead of quietly eating a row.

   THE SHELL IS THE SCROLL REGION (ASL Law 4). The document is clipped and <body> scrolls, so the scrolling
   box's block-end edge IS the lane's block-start edge by construction - no scroll offset exists that can
   park the key row under the chip. The scene layers stay fixed to the whole viewport and are unchanged:
   only the door's own content column ends higher.
   ========================================================================================================= */
:root {
  --b-lane: calc(52px + 28px + env(safe-area-inset-bottom, 0px));
  /* THIS DOOR HAS A SECOND FIXED THING AT THE BLOCK END: the foot rule. It is decoration and it is
     aria-hidden, but the host never gave it pointer-events: none, and a fixed element over a scrolling
     region collides at SOME offset, always. MEASURED at 844 x 390, where this door scrolls: the foot lay
     across the key field by 324 x 19 CSS px and across the seal by 44 x 19, and elementFromPoint in the
     middle of both returned THE FOOT - a caption was taking the tap meant for the door. It gets a band of
     its own, and it stops taking taps at all. 19px is its measured box, 14px the same gutter. */
  --b-dock: calc(19px + 14px);
}

html { overflow: hidden; }
body {
  /* the host's own `min-height: 100dvh` is the viewport, and a min-height outranks a height: it has to be
     released here or the shell keeps the whole screen and the lane is a promise the layout never keeps. */
  min-block-size: 0;
  block-size: calc(100dvh - var(--b-lane) - var(--b-dock));
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(33,29,24,.28) transparent;
}

/* SAFE CENTRING (ASL Law 8: reading order survives reflow). The shell can scroll now, and a centred column
   that outgrows its scrollport puts its own START out of reach - no scroll offset can go above zero.
   MEASURED on p4, the widest-typed of the four, before this rule went in: at 1440x900 the shell is 820 px,
   the content 1069, and the brand row sat at y -80. Every door whose content can outgrow its shell has the
   same shape available to it, so all four carry the keyword; it changes nothing at any size where the door
   fits. */
.stage { min-block-size: 100%; align-content: safe center; }
/* the door's own block-end furniture moves above the lane; the chip is not touched */
.foot { inset-block-end: var(--b-lane); pointer-events: none; }

/* ASL Law 5 and estate L6. MEASURED on the host at 390 x 844, pointer:coarse: the hairline field is
   38 CSS px tall and the vermilion seal is 2.6rem = 41.6 - both under the 44 px floor, and the floor
   outranks the fidelity of a number (brief item 21). Raised with min-block-size, never with a height, so
   the ink line and the carved stamp keep their proportions and the type does not move. */
@media (pointer: coarse) {
  .field-input { min-block-size: 44px; }
  .seal { min-block-size: 44px; min-inline-size: 44px; }
}

/* the seal sits beside the field at every width this door is rendered at, so the field brings the row
   with it without a margin to speak of. */
.field-input { scroll-margin-block: 16px 28px; }


/* =========================================================================================================
   BIND PORT - THE ASL v2 SECTION 5 MATRIX, ROW BY ROW   (R319/S312 SESSION-BIND, piece portals-p4p5p6p7)
   Brief items 26, 27, 28, 34, 35, 36. Nothing here is taste: every rule below was written against a named
   offender that tests/matrix.mjs printed on a real render of THIS door, and the run that named it is in
   evidence/portals-p4p5p6p7-r1/. The host never ran the matrix on these seven pages; bind.com does.
   ========================================================================================================= */

/* SAFE AREA (section 5 assertion 6). The harness's rule is geometric: no fixed element's CONTENT box may
   lie inside an inset band, and its own selection record names the escape - an element that pads with
   env(safe-area-inset-*) declares that its content clears the band. These layers are the scene: aria-hidden,
   pointer-events:none, painted, never read and never tapped. Padding is free for a box whose paint is a
   background or whose children are positioned against its padding box (that box does not move). It is NOT
   free for a <canvas>: the bitmap is scaled into the content box, so on a device that reports a notch the
   decorative field is rescaled by up to (47+34)/844 = 9.6 per cent in the block axis and by nothing at all
   on every device that reports no inset. That rescale was measured and accepted; the alternative was to
   leave the layers full-bleed and hand back thirty-five red rows on a rule the estate treats as a build
   failure. Nothing readable and nothing tappable is inside either band - the first flow text and every
   control are recorded per row in matrix-results.json. */
.ink, .fx, .reveal, .wash {
  padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px)
           env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
}

/* THE SKIP LINK, AND THE KEYBOARD-OPEN PROFILE. The harness refuses a FIXED element parked entirely outside
   the viewport, because at 390x430 a fixed element the layout has forgotten is exactly what strands a
   control. A skip link parked off-screen until focus is the standard pattern and it is kept - as an
   ABSOLUTE element, which is what p5's own host already does, so it belongs to the document that scrolls
   rather than to the viewport the chip is fixed to. Its offsets, its colours and its copy do not change. */
.skip { position: absolute; }

@media (pointer: coarse) {
  .skip { display: inline-flex; align-items: center; min-block-size: 44px; }
}

/* the brand mark is fixed to the block start, and clamp(1.4rem, 4vh, 2.6rem) is 33.8px at 844 - inside a
   47px notch. It is measured from the safe area now, the way the chip already is. */
.mark { top: calc(clamp(1.4rem, 4vh, 2.6rem) + env(safe-area-inset-top, 0px)); }

/* THE MARK AND THE KEYBOARD-OPEN PROFILE, measured and left alone on purpose. At 390 x 430 with the
   insets in, the mark's box is y 69..93 and the centred column's headline and subhead sit either side of
   it - it reads as clutter in that gap. Reserving its band at the stage's block start was written, tried
   and REMOVED: the shell is 316 px and the column is 270, so the 108 px reservation pushed the key input
   to y 300..344 - past the shell's own end, which is ASL Law 11's first sentence failing to buy a tidier
   masthead. Hiding the mark at that height was the other option and it costs the door its name. The
   crowding is recorded as a risk in this round's records instead of being paid for twice. */

@media (forced-colors: active) {
  .skip, .field-input, .seal { border: 1px solid ButtonBorder; }
  .skip { text-decoration: underline; }
}

@media (horizontal-viewport-segments: 2) {
  .stage { max-inline-size: 44%; margin-inline: 4% auto; }
}

/* ZOOM 200% (section 5 cross-cut), three offenders, all measured at mobileS where the layout viewport is
   160 CSS px. (1) The skip link was parked at left:-9999px, so its text box ran from -9999 to -9865 and the
   harness read it as horizontal overflow on every profile. It is parked in the BLOCK axis instead - the
   same trick p4, p6 and p7 already use - so it is off the screen without ever being off the inline axis.
   (2) The foot rule is one centred flex row of three cells whose last cell carries .42em of letter-spacing:
   at 160 px it spilled past both edges. It wraps now, and its cells may break inside a word rather than
   push the line wider - the estate's long-word rule. Neither changes at any width the matrix runs unzoomed. */
.skip { left: 1rem; top: 1rem; translate: 0 calc(-100% - 2rem); }
.skip:focus { left: 1rem; top: 1rem; translate: 0 0; }
.foot { flex-wrap: wrap; padding-inline: .6rem; }
.foot > span { min-inline-size: 0; overflow-wrap: anywhere; }
/* (3) MEASURED at 160x284, which is what mobileS and the constrained profile become at 200% zoom: the
   headline is 1.9rem with .12em of letter-spacing and "between" alone is wider than the 112 px the stage
   leaves it, so the TEXT ran 2 px past the body - the element boxes were all inside it, which is why the
   harness reported the overflow with no offender to name. The two lines of type may break inside a word
   when a word cannot fit at all; at every width above that they never do. */
/* the stage is a grid with ONE implicit column, and an implicit column is auto-sized: it grew to the
   centred column's max-content and took the body's scroll width with it - the same shape p7's deck was
   found in. minmax(0, 1fr) is the column the stage really has. The two lines of type may also break
   inside a word when a word cannot fit at all; at every width above 160 they never do. */
.stage { grid-template-columns: minmax(0, 1fr); }
.say, .sub { overflow-wrap: anywhere; }
