/* ============================================================
   THE APP'S OPENING AND ITS FRONT DOOR
   ============================================================
   The owner, 2026-09-10, ruling 11: "It's a hybrid app." Three editorial tools on
   editorial-frame.css, two social on social-dark.css, plus the Teaching Room.
   The web front door with its nine tools stays as it is; this is the app's.

   The owner, 2026-09-12: "When a user opens the app, the first thing that appears
   should be the wordmark... Animated or do something creative", and then, when
   asked whether it could wait for the Android shell, that it is a
   non-negotiable.

   THE IDEA. The mark is a pen nib and the product is about writing, so the
   opening writes. The nib lands first, alone and centred. The word draws out
   of it, left to right, at the speed of a hand. Then the whole lockup lifts to
   where a masthead sits and the six surfaces arrive beneath it. One authored
   moment, start to finish, not an effect on every element.

   THE ARTWORK IS NEVER REDRAWN. bylineAL-wordmark.svg is a fixed asset and the
   nib inside it carries the house navy-to-teal gradient. The write-on is a
   clip that uncovers that same file; nothing here re-letters it or animates a
   stroke it does not have. The nib is the left 26.2% of the viewBox (the file
   clips it to x 197..463 of a 1013.5-wide box), which is where --nib comes
   from: the reveal starts exactly where the nib ends.

   This sheet loads on top of editorial-frame.css, which gives it the tokens,
   the three faces, the theme machinery and the 44px floor. It adds a surface;
   it overrides nothing.
   ============================================================ */

/* The tokens sit on the PAGE, not on .door. They were declared on .door first
   and the opening broke in a way worth recording: .door-open is .door's
   SIBLING, so var(--nib) resolved to nothing inside it, which made the
   clip-path invalid (computed: none) and the animation shorthand invalid with
   it. The mark sat at opacity 0 on a night ground and the screen was blank. An
   undefined custom property does not fall back, it invalidates the whole
   declaration. */
body[data-tool="app"] {
  --nib: 26.2%;                     /* the nib's share of the wordmark's width */
  --write: 1250ms;                  /* the word, at the speed of a hand */
  --settle: 620ms;
  --ease: cubic-bezier(0.16, 1, 0.3, 1);   /* exponential out */
}

.door {
  min-height: 100svh;
  display: grid;
  grid-template-rows: auto 1fr;
  background: var(--paper);
}

/* ---- the opening ----
   It is a LAYER over the door, not a separate page: the door is already built
   and already readable underneath, so a reader who arrives with motion turned
   off, or with the script blocked, sees the finished screen and never a blank
   one. */
.door-open {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  background: var(--paper);
  /* The band the lockup sits on is the brand's own chrome ground, so the
     opening resolves into the header rather than cutting to it. */
}

.door-open[data-done="1"] {
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--settle) var(--ease), visibility 0s linear var(--settle);
}

.door-lockup {
  width: min(78vw, 420px);
  display: block;
}

/* ONE element, one file, two animations that do not touch the same property.
   The clip starts at the nib's own right edge, so the mark is a nib and
   nothing else until the write begins; the rise belongs to the nib, the clip
   belongs to the word. A second stacked copy was written first and thrown
   away: it needed a negative margin to line the two up, and two copies of a
   fixed asset is how a brand mark drifts out of register. */
.door-mark {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0;
  transform: translateY(6px);
  clip-path: inset(0 calc(100% - var(--nib)) 0 0);
  animation:
    door-nib-in 520ms var(--ease) forwards,
    door-write var(--write) var(--ease) 420ms forwards;
}

@keyframes door-nib-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@keyframes door-write {
  from { clip-path: inset(0 calc(100% - var(--nib)) 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

/* THE FRONT DOOR IS ALWAYS LIGHT.
   ============================================================================
   The owner, 2026-09-12: "the front door should never be in dark mode because, in
   that case, the word mark changes to All white." And then, on where the
   control belongs: "Day/night should be restricted to the main landing page
   and the tools."

   Both are the same answer. This page is a passage, not somewhere anyone reads:
   it is on screen for three seconds while the mark is written, and then it is a
   list of six names. There is no long-form text on it for a night ground to
   rest the eyes from, and the one thing it does show is the brand mark, which
   was drawn for paper. A dark ground forced a filter onto that mark, the filter
   flattened its navy and its teal to one white, and the nib fused into the "b".
   That is the tangle he called a nightmare this evening, and the ground is the
   root of it rather than the filter.

   So the page keeps the daylight tokens whatever the reader has chosen, and the
   filter is gone with nothing left for it to fix. His choice is not lost: it is
   stored, the desks behind this door honour it, and every one of them carries
   the button. THE BUTTON IS OFF THIS PAGE, which is the other half of his
   ruling and the honest consequence of the first half. A Day/Night control on a
   surface that cannot go dark changes nothing the reader can see, and a control
   that does nothing is worse than no control.

   Written as tokens on the page's own body rather than as colours on each
   element, so every rule in this file and in editorial-frame.css keeps working
   through the same names it already uses. The :has() on the root is for the
   ground behind the body, which is the only thing body cannot paint. */
[data-theme="night"] body[data-tool="app"] {
  --paper:       #F2F0EA;
  --paper-deep:  #EBE8E0;
  --paper-warm:  #F7F5F0;
  --ink:         #1A1717;
  --ink-soft:    #2F2A26;
  --quiet:       #5C544C;
  --rule:        #C9C3B7;
  --rule-soft:   #E2DCCF;
  --accent:      #156169;
  --accent-deep: #082A4C;
  --mark:        #76510F;
  --brand-rule:  #082A4C;
  --cta-fill:    #082A4C;
  --cta-ink:     #FBFAF6;
  --btn-ink:     #082A4C;
  --overlay-bg:  rgba(20,17,17,.5);
  background: var(--paper);
  color: var(--ink);
}
html:has(body[data-tool="app"]) { background: #F2F0EA; color: #1A1717; }
/* PRESS PASSES THROUGH, 2026-09-15. Press was in the selector above until this
   date, which forced the door to the grey-cream day palette while every desk
   behind it painted the warm cream. On a phone, where press is now one of the
   two themes offered, that put a seam at the exact moment somebody taps a desk:
   door #F2F0EA, desk #F4EFE5.

   Night still needs the override, because this page cannot go dark and that
   ruling is unchanged. Press never needed it: it is a daylight palette already,
   and it declares every token this sheet reads. So the door simply inherits it,
   and the ground behind the body is restated here for the same reason the day
   one above exists — the root is the only thing body cannot paint. */
html[data-theme="press"]:has(body[data-tool="app"]) { background: #F4EFE5; color: #211C17; }

/* AND THE OVERRIDE ABOVE HAS TO FOLLOW THE WIDTH. The rule is "this page cannot
   go dark, so paint it in the light palette", and since 2026-09-15 the light
   palette is not the same at every width: a phone offers press and night, a
   desktop offers all three. Painting the door in day tokens under night left a
   phone reader with a door that changed cream when they switched to night, and
   the desks did not.

   Same tokens as [data-theme="press"] in editorial-frame.css, restated rather
   than referenced because a custom property cannot be aliased to another
   theme's block. scripts/door_daylight_check.mjs compares the two directly, so
   a value that drifts out of step with that block is caught. */
@media (max-width: 699.98px) {
  [data-theme="night"] body[data-tool="app"] {
    --paper:       #F4EFE5;
    --paper-deep:  #ECE5D6;
    --paper-warm:  #FAF6EC;
    --ink:         #211C17;
    --ink-soft:    #36302A;
    --quiet:       #5F574E;
    --rule:        #C9C3B7;
    --rule-soft:   #E2DCCF;
    --accent:      #156169;
    --accent-deep: #082A4C;
    --mark:        #76510F;
    --good:        #465F36;
    --warn:        #8B4517;
    --brand-rule:  #082A4C;
    --cta-fill:    #082A4C;
    --cta-ink:     #F4EFE5;
    --body-wght:   435;
    --btn-ink:     #082A4C;
    --overlay-bg:  rgba(33,28,23,.52);
    --value-mark:  #FBF1CC;
  }
  html[data-theme="night"]:has(body[data-tool="app"]) { background: #F4EFE5; color: #211C17; }
}

/* AND THE TOKENS ARE ONLY HALF OF IT. The owner, on his own phone, 2026-09-14:
   "Footer wordmark whitened!!"

   His choice is still stamped on the root, because every desk behind this door
   honours it. Re-declaring the tokens above covers every rule that paints
   THROUGH a token and covers nothing else: a rule keyed to [data-theme="night"]
   that sets a filter, an opacity or a literal colour still fires on a page that
   renders light. The copyright wordmark is exactly that — editorial-frame.css
   whitens it for the near-black tools, which is right there and paints a white
   mark on cream here.

   The root's own colour is the same class of fault, found by the same gate:
   anything on this page that inherits its colour rather than reading --ink was
   taking the night ink. It is set on the line above.

   scripts/door_daylight_check.mjs walks the whole door in all three stored
   themes and compares what the browser computed, so the next rule of this kind
   is caught rather than photographed. */
/* Press came out of this selector on 2026-09-15 with the ruling above: the
   only rule that whitens this mark is keyed to night (editorial-frame.css
   2800), so under press there was never anything here to undo. */
[data-theme="night"] body[data-tool="app"] .tool-foot .copy img {
  filter: none;
  opacity: 1;
}

/* HOW IT ENDS. The owner picked B from three, watched side by side: the rule
   draws, the hand lifts, or it just settles.

   The rule is gone, and its only defence is worth recording because it lost.
   A navy line under the lockup could have been the masthead's own rule
   arriving early, so the layer would lift off it and the same line would still
   be there under the header: a seam rather than a flourish. He did not want
   it, and an underline that is not that seam is decoration.

   What ends it instead is the gesture the mark already implies. The whole
   lockup rises a little and eases out, which is a hand coming off the page at
   the end of a stroke. Nothing new is drawn on the screen to do it. */
.door-lockup { will-change: transform, opacity; }
/* THE WORD HOLDS BEFORE THE HAND LIFTS. The owner, 2026-09-12, watching it on his
   phone: "the wordmark doesn't even land. Blink and you miss."

   He was right and the arithmetic is damning. The write runs 420 to 1670ms and
   the lift began at 1740, so the finished wordmark existed for SEVENTY
   MILLISECONDS. The moment had no landing in it at all: the word arrived and
   was already leaving. Nothing measured that, because nothing measures a beat
   of stillness; only watching it does.

   The lift now starts at 2500ms, which gives the whole word 830ms on screen
   before anything moves. The write itself is unchanged. */
.door-open .door-lockup { animation: door-lift 620ms var(--ease) 2500ms forwards; }

/* THE GREETING, AND THE EXTRA SECOND IT BUYS ITSELF, 2026-09-18.
   Ritu: "the word mark and the line should hang around for just that wee bit so
   that the user can read it."

   The line is set in the reading serif rather than the display face the
   wordmark is drawn in. It is a sentence spoken to one person, and setting it
   in the brand's own voice would make it read as a second piece of branding
   under the first. It takes the lockup's own width so its left edge stands on
   the same line the mark starts from, which is the one alignment on this
   screen.

   TIMED AGAINST THE OPENING'S OWN CLOCK. The word halts at 1670ms. The line
   fades in from 1750 over 320, so it arrives onto a still screen instead of
   racing the hand, and the lift below waits for --hello-lift.

   4400ms SINCE 2026-09-18, FROM 3500. Ritu, having read it on his own phone:
   "The opening message doesn't stay long enough for the user to read." At 3500
   the line was fully on screen and motionless for 1430ms, which is one pass
   over about seventy characters at a pace nobody reading a launch screen is
   using. 4400 leaves it steady for 2330ms and takes the whole opening to
   5020ms against the 3120 it runs without a greeting. A reader who does not
   want to wait taps through it, which the opening has always allowed.

   ONE NUMBER, NOT TWO. The lift is a CSS animation and the controller in
   app.html keeps a backstop behind it, and until now each carried its own copy
   of this figure. door-greeting.js reads the property below rather than holding
   a second one, so moving it here moves both.

   NOTHING HERE FIRES ON ITS OWN. The attribute and the class are both written
   by assets/js/door-greeting.js, and only after the session has named the
   reader in time. A launch with no greeting keeps every number above it. */
/* AN EMPTY SLOT IS NOT A ROW. The opening is a grid with place-items: center,
   so a second child splits the screen in two and centres the mark inside the
   top half. MEASURED before this line: an empty <p> moved the wordmark from the
   centre of the screen to 331px above the foot on a 760px screen, on every
   launch, greeting or no greeting. The slot ships in the markup so that a
   script arriving late has somewhere to write; until it holds a sentence it
   must not exist to the layout. */
.door-hello:empty { display: none; }
/* With a line in it the two are ONE lockup, held together and centred as a
   pair, which is the shape the sign-in screen below already takes. */
.door-open[data-hello="1"] {
  grid-auto-rows: max-content;
  align-content: center;
  gap: var(--s-4);
}
.door-hello {
  width: min(78vw, 420px);
  margin: 0;
  opacity: 0;
  /* CENTRED ON THE MARK, AND ON ITS INK RATHER THAN ITS BOX. Ritu, on the first
     rendered version: "Should be aligned too", and then, seeing it set flush
     left under a centred mark: "guess not being centred with the wordmark is
     the visual issue that I am feeling."

     MEASURED from the asset. The wordmark's viewBox runs 197 to 1210.5, and its
     ink runs 209 to 1186.51: twelve units of air on the left and twenty-four on
     the right. So the drawing's own centre sits half that difference, six units
     of 1013.5, left of the middle of the file it is served in. Centring the
     text on the box would leave it 1.65px right of the mark at 358.

     padding-right of 1.18 per cent moves the text's centre left by half of it,
     which is exactly those six units at any width. The image and this text
     share a width, so the correction holds on every screen without a number
     that has to be recalculated. */
  text-align: center;
  padding-right: 1.18%;
}

/* TWO LINES, NOT ONE. Ritu: "The user's name in one line, the quip second."
   The first line is the address and takes the door's own strongest move, the
   display face in brand navy, which is what the statement on the door itself is
   set in. The second is the remark and stays in the reading serif at full ink.

   HIS WORDS FOR IT WERE "visually uninspiring and grey", and grey was the
   literal fault: the whole greeting was set in --quiet, the tone this product
   uses for captions and undertexts. A sentence addressed to a person by name is
   not a caption. Nothing new was introduced to fix it; both colours and both
   faces are already on this screen. */
/* NO WORD LEFT ALONE ON A LINE. Ritu, 2026-09-19, off his phone: '"Ritu"
   widowed, "sentence" widowed. The full stop after Ritu (cringe).'

   MEASURED at 358, which is where his 412px phone lays out once he zooms: the
   text column is 275px and both lines broke one word short. The address set
   "Good to see you," then "Ritu." alone, and the quip set "The hard part is
   still the first" then "sentence." alone. At 390 and 412 both fitted on one
   line, which is why every gate this product runs had been green.

   Cutting the stop from the copy fixed the address by itself: MEASURED after,
   "Good to see you, Ritu" sets on one line at 358. text-wrap: balance holds
   the rest, evening the lines instead of filling the first and pushing the
   remainder down, so nothing is stranded when a line does have to break.

   The stop also ended a second fault. It sat OUTSIDE the name's span, so it
   painted navy against the teal name it followed. MEASURED: name
   rgb(21,97,105), stop rgb(8,42,76). */
.door-hello__to {
  text-wrap: balance;
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-6);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.1;
  color: var(--accent-deep);
}
/* THE NAME IN THE MARK'S OTHER COLOUR. Ritu chose this of three rendered on
   2026-09-18: the wordmark is a navy word with a teal ending, and the greeting
   says the reader's name in the same teal, so the line reads as that mark
   speaking rather than as a caption near it. MEASURED against the paper:
   6.26:1 for the teal, against 12.74:1 for the navy beside it and a 4.5 floor.
   The teal is --accent, already on this screen in the mark and on the tab bar
   behind it; nothing new was introduced to make this. */
.door-hello__name { color: var(--accent); }

.door-hello__quip {
  text-wrap: balance;
  display: block;
  margin-top: var(--s-2);
  font-family: var(--font-body);
  font-size: var(--t-4);
  line-height: 1.45;
  color: var(--ink);
}
.door-hello.is-in { animation: door-hello-in 320ms var(--ease) forwards; }
@keyframes door-hello-in {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}
.door-open[data-hello="1"] { --hello-lift: 4400ms; }
.door-open[data-hello="1"] .door-lockup { animation-delay: var(--hello-lift); }
@keyframes door-lift {
  to { transform: translateY(-10px); opacity: 0.9; }
}

/* ---- the door itself ---- */
.door-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-2);
  padding: var(--s-1) calc((100% - var(--frame)) / 2);
  min-height: 60px;                 /* the height every masthead in the app holds */
  background: var(--paper);
  border-bottom: 2px solid var(--brand-rule);
  position: sticky;
  top: 0;
  z-index: 30;
}
/* 40px, one number across the whole product. The owner, 2026-09-12, holding up a
   photograph of the editorial landing masthead on his phone: "The wordmark on
   an app should be at least this size". Measured on live prod at 360, what he
   photographed renders 36px, so that is the floor. Measured the ceiling on the
   tool bars: at 46px the brand column squeezes the story control from 92 to
   80px and the story name truncates; 40 is the largest that fits. Full table
   in editorial-frame.css. */
.door-head img { height: 40px; width: auto; display: block; }

/* THE DESCRIPTOR, beside the mark. The owner, 2026-09-13, on the space next to the
   wordmark on this page: "Write 'Smart Writing Workflows' there. Take the exact
   font and style from the desktop version."

   The declarations are the desktop's, copied rather than referenced, because
   this page does not load front-door.css. They are .masthead .descriptor in
   assets/css/front-door.css line 220: display face, --t-1, weight 500,
   --track-caps, uppercase, --ink-soft, a 1px rule on its left with --s-3 of
   padding, and the leading four steps of the baseline grid. The two must be
   changed together, which is what tests/app-door-descriptor.test.js holds. */
.door-head .brandcluster { display: flex; align-items: center; gap: var(--s-3); }
.door-head .descriptor {
  font-family: var(--font-display);
  font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  font-weight: 500;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink-soft);
  line-height: calc(var(--s-1) * 4);
  padding-left: var(--s-3);
  border-left: 1px solid var(--rule);
  white-space: nowrap;
}

/* ON A PHONE THE LINE TAKES TWO, and everything else about it is the desktop's.
   MEASURED, set exactly as the front door sets it: the words come to 248.2px
   and the mark beside them to 111.4px, so with the rule, its padding and the
   gap the pair needs a 440px screen. His own phone lays out at 358, and the
   line ran off the edge reading "SMART WRITING WORKF". The type cannot give:
   12px is the floor scripts/text_floor_check.mjs holds, and shrinking to fit
   would need 8px.

   So the break gives instead. Same face, same 12px, same weight, same capitals,
   same tracking, same colour, same rule down its left; only the line breaks
   after WRITING. Two lines of this type are 32px, inside the 60px every
   masthead in the app holds, so the bar does not grow. */
@media (max-width: 699.98px) {
  .door-head .descriptor { white-space: normal; }
}

/* The day/night button used to be repeated here against this header. It came
   off the front door on 2026-09-12 with the ruling above: this page cannot go
   dark, so the control had nothing to change. Its rules are deleted rather than
   left behind, because a stylesheet that dresses a control no page mounts is
   how the next person concludes the control exists. */


/* THE COLUMN MOVED OFF THIS BOX AND ONTO WHAT IT HOLDS, 2026-09-13, and it is
   not a preference. The owner chose the grouped door with the social desks on their
   own ground, and a ground that stops at the text column reads as a card, which
   this page deliberately does not use. A band has to reach both edges of the
   screen.

   The usual full-bleed trick, margin-inline: calc(50% - 50vw), is wrong here:
   100vw counts the scrollbar, so on a desktop with one the page gains about
   15px of sideways overflow, which the overflow gate reports at 1440. So the
   padding sits on the children instead, and every one of them resolves
   calc((100% - var(--frame)) / 2) against the full width of the page, exactly
   as .tool-masthead and the pinned action bar already do. */
.door-body {
  padding: var(--s-5) 0 var(--s-6);
  display: grid;
  gap: var(--s-5);
  align-content: start;
}
.door-line,
.door-room { padding-inline: calc((100% - var(--frame)) / 2); }

/* THE APP'S DOOR IS A DOOR, NOT A PAGE, AND IT KEEPS ITS WIDTH ON A DESKTOP.
   Ritu, 2026-09-19, with a screenshot of it on his laptop: "This is where I
   landed on my desktop. Am going crazy."

   MEASURED at 1440: every row ran 1283px wide, and at 1920 it ran 1296. That is
   the shared editorial frame, which is right for a page of copy and wrong for a
   list of seven names each one line long. A desk name at 19px with 1200px of
   nothing after it reads as a broken layout, which is exactly what he saw.

   40rem, not the suite's 72. The door holds the same column as its own sign-in
   screen, which is 540px of text under a 420px mark, so the two screens of this
   surface agree with each other instead of one spreading and one not. Below
   900px nothing changes: the phone layout this was built for is untouched, and
   its own rules already start at 699.98px. */
@media (min-width: 900px) {
  :root { --frame: min(40rem, 100% - var(--gutter) * 2); }
}

/* THE DOOR'S ONE STATEMENT, AND IT IS SET AS ONE. Until 2026-09-16 the whole
   sentence sat in the body serif at 16px in the quiet grey, which made the only
   thing the page says the quietest thing on it: every desk name below is set in
   the display face at full ink and simply won. The owner asked for more
   prominence and chose this of four rendered treatments.

   The face is the door's own. The desk names already carry it, so the statement
   is speaking in the page's voice rather than borrowing a new one. */
/* 28px, DOWN FROM 40, 2026-09-18. The owner, on a photograph of his own phone:
   the line "is a bit too large. It almost obliterates the wordmark."

   MEASURED at 358 before the change: the statement was set at 40px and the
   wordmark beside it is exactly 40px tall, so the two carried equal weight and
   the mark lost, being a drawing against a bold navy sentence. The desk names
   under it are 19px. At 28 the statement sits under the mark and still leads
   the names.

   28 AND NOT 23, and the reason is a gate rather than a preference. A headline
   is a ramp here and leading_check.mjs skips display type at or above 24px
   (DISPLAY_AT). This line runs a 1.02 leading, which is right for display and
   far outside the 1.47 to 1.60 band that gate holds prose to, so at 23px it
   would be judged as prose and fail. The next step down the scale is the one
   that changes what the line IS.

   IT COSTS 16px OF THE PINNED BAND, not saves it: the new sentence is longer
   and takes two lines at 358 where "Seven desks" took one, so the band goes
   from 196.8 to 213.1 of a 760px screen. Four desk names still open above the
   fold. Measured at 358 and 412; both wrap the same way.

   ONE LINE SINCE 2026-09-18, AND IT COST TWO WORDS. He saw the 28px version
   live: "From one-line hero to two: not good at all. I was expecting it to be
   in one line."

   ONE LINE IS ARITHMETIC, not taste. MEASURED: "An editor for every kind of
   story." sets 412px wide at 28px, and the text column is 288px at 320 and
   310px at 358. There is no phone it fits on at that size, and the largest it
   holds one line at 320 is 20px, one step above the desk names. He was shown
   that, and two shorter sentences that keep hero-sized type, and chose to cut
   the sentence instead: "An editor for every story."

   --t-5 IS 23px AND THE MEASURED CEILING AT 320 IS 24. That is my call, not
   his: he picked the sentence off a render set at 25, and 25 is not a step on
   the nine-size scale this product spent 2026-08-12 closing. 23 is, it clears
   the ceiling at every width from 320 up, and 2px is not a thing anybody can
   see. The band is 181.5px against 213.1, and six desk rows open above the fold
   instead of four. */
/* THE STATEMENT IS ITS OWN ELEMENT, 2026-09-18, and the reason is a gate
   telling the truth. At 28px this paragraph was display type and
   leading_check.mjs skipped it, which is right: a headline is a ramp and 1.02
   is its leading. At 23px it is under that gate's 24px threshold, so the whole
   paragraph was judged as prose and its display leading failed the 1.35 floor,
   four times.

   Setting a prose leading on a display line to quiet the gate would be the
   wrong repair. The paragraph holds TWO things that are set differently: a
   statement and an explanation. Each carries its own now, and the gate can
   judge each correctly. The statement draws one line box, which is the gate's
   own stated exclusion ("leading between lines that do not exist is not a thing
   a reader can see"), and the explanation keeps the 1.5 it always had. */
.door-line {
  margin: 0;
  line-height: 1.5;
}
.door-line__say {
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-5);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.02;
  /* BRAND NAVY, on the owner's word, 2026-09-16: "the word SEVEN DESKS can be a
     brand colour. Maybe navy?" It is --accent-deep, #082A4C, and it is the same
     navy as the 2px rule sitting directly above this line in the masthead, so
     the statement and the rule read as one piece of brand rather than two
     unrelated marks. Measured 12.67:1 against the paper, against 14.74:1 for
     the ink it replaces: both far past the floor, so nothing is traded.
     The explanation below keeps --quiet; only the statement takes the colour. */
  color: var(--accent-deep);
  max-width: none;
}

/* The explanation keeps the reading voice it always had: same serif, same size,
   same grey, same 34ch measure. It is a block inside the same paragraph, so it
   travels with the statement and the sentence is still one sentence to a
   screen reader and to anyone selecting it. */
.door-line__rest {
  display: block;
  margin-top: var(--s-2);
  font-family: var(--font-body);
  font-size: var(--t-3);
  font-weight: 400;
  letter-spacing: 0;
  line-height: 1.5;
  color: var(--quiet);
  max-width: 34ch;
}

/* Not cards. Each surface is a row with a rule under it, which is how a
   contents page works and how this product already sets its lists. */
.door-list { display: grid; gap: 0; }

.door-item {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: baseline;
  gap: var(--s-2) var(--s-3);
  padding-block: var(--s-3);
  border-top: var(--hairline) solid var(--rule-soft);
  text-decoration: none;
  color: inherit;
  min-height: var(--touch-min);
  transition: background 160ms var(--ease);
}
.door-list > .door-item:last-child { border-bottom: var(--hairline) solid var(--rule-soft); }

/* ── The rooms, each with its name in a band of its own colour ─────────────
   The owner, 2026-09-13, choosing between four rendered doors and then sending back
   the one he meant: the room's name in a saturated band, the page daylight
   throughout.

   The colours are the product's own. Editorial takes this sheet's --accent.
   Social takes the riso pink of social-dark.css's --mark, darkened from #FF7FB2
   to #B0437A because the light original on a light band fails the text floor;
   tests/app-door-contrast.test.js holds all three pairs. Memory takes --mark.

   The band sits on the content column, not edge to edge: its room block already
   pads itself in, so the band starts and stops where the words do. */
.door-group {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  font-weight: 400;
  padding: 6px var(--s-3);
  background: var(--room-tone);
  color: var(--paper-warm);
}
/* The band is the division. The row under it carries no rule of its own, or
   the band would be underlined by a hairline it does not need. */
.door-group + .door-item { border-top: 0; }

/* ONE NOTCH DOWN, 2026-09-14. The owner: "on the landing page, there's too much of
   a gap between the sections. You can bring memory a little up and social too.
   Then, if we add [a desk] to the menu, social doesn't go up. Only memory can
   go up a notch to maintain an equidistance from each section."

   MEASURED at 358 before this: 24px of padding under a room, 24px of margin,
   and 24px of padding over the next, so 72px of nothing between the last desk
   of one room and the next room's band. Now 48px, and the three rooms stay
   equidistant, which is the half of his sentence that governs.

   Taking one step off each gap moves Social up by 24px and Memory by 48, which
   is his "only memory can go up a notch": a room that sits after two gaps
   gains twice what a room after one gains. Nothing is spaced per room. */
.door-room { padding-block: var(--s-3); }
/* The page's own opening air already stands above the first room; its own
   padding on top of that reads as a gap nobody put there. */
.door-room:first-child { padding-top: 0; }
.door-room + .door-room { margin-top: var(--s-3); }
/* A row's suite label belongs to its room, so it takes the room's colour:
   INSTAGRAM and X reading in the page's teal underneath a pink band is the
   label pointing at the wrong room. */
.door-room .door-suite { color: var(--room-tone); }
.door-room--editorial { --room-tone: var(--accent); }
.door-room--social    { --room-tone: #B0437A; }
.door-room--memory    { --room-tone: var(--mark); }
.door-room > .door-item:last-child { border-bottom: 0; }

.door-name {
  font-family: var(--font-display);
  font-variation-settings: "wght" 700;
  font-size: var(--t-4);
  letter-spacing: -0.01em;
  color: var(--ink);
}
.door-what {
  grid-column: 1 / -1;
  font-family: var(--font-body);
  font-size: var(--t-2);
  color: var(--quiet);
}
.door-suite {
  font-family: var(--font-mono);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}

@media (hover: hover) {
  .door-item:hover { background: var(--paper-warm, rgba(0,0,0,.03)); }
  .door-item:hover .door-name { color: var(--accent); }
}
.door-item:active { transform: scale(var(--press-light)); }
.door-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* The surfaces arrive under the lockup, one after another, close enough
   together to read as one movement. Each starts VISIBLE and is offset, so a
   stalled script leaves a readable page rather than an empty one. */
.door-item { animation: door-item-in 500ms var(--ease) both; }
.door-item:nth-child(1) { animation-delay: 2580ms; }
.door-item:nth-child(2) { animation-delay: 2650ms; }
.door-item:nth-child(3) { animation-delay: 2720ms; }
.door-item:nth-child(4) { animation-delay: 2790ms; }
.door-item:nth-child(5) { animation-delay: 2860ms; }
.door-item:nth-child(6) { animation-delay: 2930ms; }
@keyframes door-item-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* A reader who has asked for less motion gets the finished screen, at once,
   with nothing moving. Not a shorter animation: none. */
@media (prefers-reduced-motion: reduce) {
  .door-mark, .door-lockup, .door-item { animation: none !important; }
  /* Reduced motion is not no moment, which is what the opening controller in
     app.html already says in these words. The greeting is simply there, held
     still, and door-greeting.js gives the cover its own longer backstop so it
     can be read. */
  .door-hello.is-in { animation: none !important; opacity: 1; transform: none; }
  .door-mark { opacity: 1; transform: none; clip-path: inset(0 0 0 0); }
  .door-lockup { transform: none; opacity: 1; }
  .door-item { opacity: 1; transform: none; }
  .door-open[data-done="1"] { transition: none; }
}

/* ============================================================
   THE DOOR HOLDS ITS HEAD WHILE THE DESKS SCROLL, 2026-09-13.
   ============================================================
   AT THE FOOT OF THE SHEET ON PURPOSE. This block was first written into the
   phone media query 250 lines above, where .door-body's own padding is declared
   AFTER it at the same specificity and therefore beat it: measured, the line
   still sat 40px down the page at rest and slid those 40px before it pinned.
   Same selector, same specificity, later in the file wins. */
@media (max-width: 699.98px) {
  /* THE MASTHEAD AND THE LINE HOLD; ONLY THE DESKS MOVE. The owner, 2026-09-13:
     "Is it scrollable or not? Asking because the bands now exceed a one-glance
     page", and then "They must be scrollable without the masthead and the
     description below moving, as in the tools."

     MEASURED at 358 with a 700px screen: the door is 1264px, so it scrolls by
     564px, and it did before the bands too — they added about 150 of it.

     Sticky, not fixed, and the difference is one line of padding. A sticky box
     travels until it reaches its offset, which is why the tools' hero had to be
     fixed: it sat 32px down the page and slid those 32px first. This line sits
     directly under the masthead with nothing above it, so its offset IS where
     it already is and it never moves at all. Staying in the flow also means the
     page needs no compensating padding and no inner scrolling region, which is
     the shape that produced the "region that can never scroll" finding parked
     in docs/BACKBURNER.md.

     The opening air it used to take from .door-body comes onto the line itself,
     because any gap above it is travel. The background is not decoration: rooms
     scroll under this line, and the line is full width, so it has to be opaque
     from edge to edge. z-index 29 sits under the masthead's 30. */
  .door-body { padding-top: 0; }

  /* ── THE FOLD, 2026-09-17 ─────────────────────────────────────────────────
     Ritu, on a photograph of his OnePlus 13: "this is how the front door looks
     as soon as it opens. The Social bar is half visible", and then: fix it
     with "the gap problem that I flagged earlier".

     MEASURED at 412x734, which is his phone in Chrome once the URL bar and the
     navigation bar are taken off the 905px screen:

       masthead                        60px
       the pinned sentence            137px
       chrome before any desk         197px, 27% of the screen
       each desk row                   93px
       SOCIAL's band landed at        690px, with 13px of its first row showing

     Seven desks cannot fit on 734px and the page has to scroll. The fault was
     never the scrolling: it was that the fold landed ON a coloured band and
     cut it, which reads as broken rather than as "more below".

     TWO OF HIS EARLIER RULINGS ARE DELIBERATELY NOT TOUCHED. The pinned band
     keeps the whole sentence (2026-09-16: "I did not mean yes to the smaller
     band"), and the spacing between rooms stays where he set it (2026-09-14).
     So the space comes from the two places he has not ruled on: the dead cream
     under the sentence, and the padding inside a row.

     AFTER: SOCIAL's band at 642px with 61px of FrameWork's row beneath it, and
     the page 72px shorter. Phone widths only; the desktop door is untouched. */
  .door-body { gap: var(--s-4); }
  .door-line { padding-bottom: var(--s-3); }
  /* 85px a row, from 93. The 44px touch floor is var(--touch-min) and is still
     met with 41px to spare, which scripts/phone_balance_check.mjs measures. */
  .door-item { padding-block: 12px; }

  /* THE WHOLE SENTENCE STAYS PINNED, which is the owner's ruling of
     2026-09-16. He was offered a version that pinned only "Seven desks." and
     left the explanation to scroll, which measured an 81px band against
     today's 112, and he declined it. Measured with the split: 139px pinned,
     leaving 451px of a 650px screen for the desk list against 478px before. */
  .door-line {
    position: sticky;
    top: 60px;
    z-index: 29;
    background: var(--paper);
    padding-block: var(--s-4) var(--s-3);
    margin: 0;
    /* THE BAND IS THE WIDTH OF THE SCREEN, whatever the words measure. The base
       rule caps this line at 34ch for the reading measure, and a capped box
       paints a capped background: rendered scrolled, the rules and marks of the
       rooms passing underneath showed through the space to its right. At phone
       width the column is 288 to 364px against a 34ch measure of about 290, so
       the cap is doing almost nothing here anyway. */
    max-width: none;
  }
}

/* ============================================================
   THE SIGN-IN SCREEN, 2026-09-15.

   It is the opening layer that stays. The same fixed cover, the same paper
   ground, the same wordmark in the same place, so the screen a person meets
   before they sign in is the screen they already know. What changes is that
   this one waits for them instead of dissolving.

   NOT A CARD. A card would be a box floating on a ground that is already the
   product's own; the ground IS the surface here, and one column on it is the
   whole composition. Nothing is nested inside anything.
   ============================================================ */
/* IT IS NOT A SECOND COVER. The login lives inside the opening layer, under
   the lockup that has just come to a halt, so the sequence a person sees is
   the one the owner named twice: the wordmark writes, it stops, the login appears.
   The first build made it a separate fixed cover and the landing page flashed
   for six hundred milliseconds in between. */
.door-signin {
  width: 100%;
  padding: 0 calc((100% - var(--frame)) / 2);
  box-sizing: border-box;
  display: flex;
  justify-content: center;
}
.door-signin[hidden] { display: none; }

/* THE HALT. With nobody signed in the opening does not lift: the animation
   that would carry the lockup away is cancelled, and the word stays where it
   finished. The lockup also gives up the vertical centring it held while it
   was the only thing on the screen, so the pair sits as one column. */
.door-open[data-hold="1"] .door-lockup { animation: none; opacity: 1; transform: none; }
.door-open[data-hold="1"] { grid-auto-rows: max-content; align-content: center; gap: var(--s-6); }

/* The login rises once, from where it already is. One authored moment, not an
   entrance on every part. */
@keyframes door-signin-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
/* THE ROW IS TAKEN BEFORE IT IS FILLED. See the note in app-signin.js: the
   wordmark used to jump 88px upward in one frame when this layer appeared,
   MEASURED at 1440 and at 358. The layer is now un-hidden the moment the
   session answers, which is inside a tenth of a second, and held invisible
   until the word has finished writing. visibility, not display, because a row
   that takes no space is a row that has not been reserved. */
.door-signin[data-waiting="1"] { visibility: hidden; }
.door-open[data-hold="1"] .door-signin:not([data-waiting="1"]) { animation: door-signin-in 520ms var(--ease) both; }
@media (prefers-reduced-motion: reduce) {
  .door-open[data-hold="1"] .door-signin { animation: none; }
}

/* CENTRED, AND THAT IS THE WHOLE POINT OF THE SCREEN. A page with exactly one
   action is a threshold, not the top of a document: set left it reads as the
   beginning of something to scroll, set on the centre line it reads as a door
   to pass through. The owner had no preference between three rendered directions,
   so this is my call and the reasoning is here. The two it beat: a panel
   floating over a dimmed landing page, which puts a card on a ground that is
   already the product's own and shows a page nobody can read; and dropping the
   title to set the line large, which loses "Smart Writing Workflows", his own
   words from the door. */
.door-signin__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  max-width: 30rem;
  width: 100%;
}

/* The refusal sits ABOVE the button, where a person reads it before they act
   again, and it takes the warn tone rather than an alarm: a refused sign-in is
   a closed door, not a fault they caused. */
.door-signin__refused {
  font-family: var(--font-body);
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--ink-soft);
  border-top: 1px solid var(--warn, var(--rule));
  padding: var(--s-3) 0 0;
  /* AIR UNDER IT, 2026-09-18. Ritu, on a photograph of the refused screen: the
     text is "too tight, almost brushing the Google pill."

     MEASURED at 320, 358 and 412 before this: 0px between the sentence's box
     and the button. The stack is a flex column with no gap, this rule carried a
     margin on three sides and nothing on the fourth, and the button sat
     directly on the last line. The 24px matches the air ABOVE the rule, so the
     sentence is held by an equal measure top and bottom instead of being
     pushed onto the control it is about. */
  margin: var(--s-4) 0;
  max-width: 24rem;
}
.door-signin__refused[hidden] { display: none; }

/* GOOGLE'S BUTTON, TO GOOGLE'S RULES. Their wording, their mark, their
   minimum size, because Play reviews it. It deliberately does not wear this
   product's pill: a sign-in button that looks like our own controls is the one
   place where matching the house style would be the wrong call. */
.door-signin__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 48px;                 /* above the 44px floor the phone gates hold */
  margin: 0;
  padding: 0 var(--s-4);
  background: #FFFFFF;
  color: #1F1F1F;                   /* Google's own ink on their own white */
  border: 1px solid #747775;        /* their stroke, which carries the contrast */
  border-radius: var(--pill);
  font-family: var(--font-display);
  font-size-adjust: var(--x-adjust);
  font-size: var(--t-3);
  font-weight: 500;
  letter-spacing: 0;                /* their wording is set plain, not tracked */
  text-decoration: none;
  transition: border-color 0.18s ease, box-shadow 0.18s ease,
              transform var(--press-out, 0.18s) var(--press-ease, ease);
}
@media (hover: hover) {
  .door-signin__btn:hover { border-color: #1F1F1F; box-shadow: 0 1px 3px rgba(26, 23, 23, 0.16); }
}
.door-signin__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.door-signin__btn:active { transform: scale(var(--press-light, 0.99)); }
.door-signin__google { display: inline-flex; }

@media (prefers-reduced-motion: reduce) {
  .door-signin__btn { transition: none; }
  .door-signin__btn:active { transform: none; }
}

/* THE SECOND WAY, and it is a button because he asked for one. It is the
   product's own pill, not Google's, and it is quieter than the sign-in button
   beside it: one action is still the action, and this is the answer for the
   person that action does not fit. */
.door-signin__ask {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  margin: var(--s-3) 0 0;
  padding: 0 var(--s-4);
  background: transparent;
  color: var(--ink-soft);
  border: var(--hairline) solid var(--rule);
  border-radius: var(--pill);
  font-family: var(--font-display);
  font-size-adjust: var(--x-adjust);
  font-size: var(--t-2);
  font-weight: 500;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  text-decoration: none;
  transition: border-color 0.18s ease, color 0.18s ease;
}
@media (hover: hover) {
  .door-signin__ask:hover { border-color: var(--accent); color: var(--accent); }
}
.door-signin__ask:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ============================================================
   THE REQUEST PAGE. The same paper, the same mark, the same column as the
   sign-in screen it is reached from, because it is the other half of that
   screen rather than somewhere else in the product.
   ============================================================ */
.reqpage {
  /* The masthead above it takes 60px, so the page is what is left of the
     screen rather than a whole screen pushed down by a bar. */
  min-height: calc(100dvh - 60px);
  /* More above the heading than below it, and the heading is the first thing
     under the rule now. */
  background: var(--paper);
  display: flex;
  justify-content: center;
  padding: var(--s-6) calc((100% - var(--frame)) / 2);
  box-sizing: border-box;
}
.reqpage__inner { width: 100%; max-width: 30rem; }

.reqpage__back {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-link);
  font-family: var(--font-display);
  font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--quiet);
  text-decoration: none;
  border-bottom: var(--hairline) solid transparent;
}
@media (hover: hover) { .reqpage__back:hover { color: var(--accent); border-bottom-color: var(--accent); } }
.reqpage__back:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }


/* In the masthead, opposite the mark. 44px because it is a control in a bar a
   thumb reaches for, not a link in a paragraph; the bar is 60px, so it fits. */
.door-head .reqpage__back { min-height: var(--touch-min); margin: 0; }

/* AND ON A NARROW PHONE THE DESCRIPTOR GOES, NOT THE MARK. This bar carries
   three things where the door carries two, and a 60px masthead is one number
   across the whole product. Measured on this page with the fonts loaded: the
   line holds two rows at 375 and above and breaks to three at 360 and below,
   which is where the owner's own phone renders. The mark is the one thing that
   never goes; the descriptor is the one with no function, so it is the one
   that does. */
@media (max-width: 374.98px) {
  .reqpage-head .descriptor { display: none; }
}

.reqpage__title {
  font-family: var(--font-display);
  font-size-adjust: var(--x-adjust);
  font-size: var(--t-5);
  font-weight: 600;
  letter-spacing: var(--track-tight);
  line-height: 1.12;
  color: var(--ink);
  margin: 0;
}
.reqpage__line {
  font-family: var(--font-body);
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--quiet);
  margin: var(--s-2) 0 0;
}

.reqpage__form { margin: var(--s-5) 0 0; }
.reqfield { margin: 0 0 var(--s-4); }
/* THE MARK SITS ON THE LABEL'S LINE, not on one of its own. A row, because a
   <div> is not allowed inside a <label> and because the mark has to be a
   sibling of the label to sit beside it. This is the shape .input-label
   already has on the desks; the 2px nudge below is that sheet's own, and it
   exists because a mark on a label row measures 10px above its box while a
   mark alone above a box measures 6px. Both are wanted at 8px.
   scripts/box_tools_align_check.mjs measures it and found this page at 16px. */
.reqfield__labelrow {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  margin: 0 0 var(--s-2);
}
.reqfield__labelrow .reqfield__label { margin: 0; }
/* 6px, measured, not chosen. The nudges in editorial-frame.css (-2px alone
   above a box, +2px on a desk's label row) are differences between that row's
   height and the 8px everything is tuned to, and this row is a third shape:
   its own height is the 28px of the mark's box, where a desk's label row is
   the height of its text. Measured here at 390px: 14px with no nudge, 12px at
   +2, 8px at +6. Re-measure with scripts/box_tools_align_check.mjs before
   changing it. */
.reqfield__labelrow .box-tools { margin-left: auto; top: 6px; }

.reqfield__label {
  display: block;
  font-family: var(--font-display);
  font-size-adjust: var(--x-adjust);
  font-size: var(--t-1);
  font-weight: 700;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 var(--s-2);
}
.reqfield__input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  min-height: var(--touch-min);
  padding: var(--s-2) var(--s-3);
  font-family: var(--font-body);
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--ink);
  background: var(--paper-warm);
  border: var(--hairline) solid var(--rule);
  border-radius: var(--box);
}
.reqfield__input--area { resize: vertical; min-height: calc(var(--s-1) * 26); }
.reqfield__input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent); }
.reqfield__input::placeholder { color: var(--quiet); }
.reqfield__note {
  display: block;
  font-family: var(--font-body);
  font-style: italic;
  font-size: var(--t-2);
  line-height: 1.45;
  color: var(--quiet);
  margin: var(--s-1) 0 0;
}

/* The error names the problem and the recovery, and sits where the eye already
   is: under the fields, above the button that failed. */
.reqpage__error {
  font-family: var(--font-body);
  font-size: var(--t-3);
  line-height: calc(var(--s-1) * 6);
  color: var(--ink-soft);
  border-left: 1px solid var(--warn, var(--rule));
  padding-left: var(--s-3);
  margin: 0 0 var(--s-4);
}
.reqpage__error[hidden] { display: none; }

.reqpage__submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 var(--s-5);
  background: var(--btn-ink, var(--navy));
  color: var(--paper);
  border: 0;
  border-radius: var(--pill);
  font-family: var(--font-display);
  font-size-adjust: var(--x-adjust);
  font-size: var(--t-2);
  font-weight: 500;
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  cursor: pointer;
  transition: opacity 0.18s ease, transform var(--press-out, 0.18s) var(--press-ease, ease);
}
.reqpage__submit:disabled { opacity: 0.6; cursor: default; }
.reqpage__submit:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.reqpage__submit:active:not(:disabled) { transform: scale(var(--press-light, 0.99)); }

.reqpage__done { margin: var(--s-5) 0 0; }
.reqpage__done[hidden] { display: none; }
.reqpage__donetitle {
  font-family: var(--font-display);
  font-size-adjust: var(--x-adjust);
  font-size: var(--t-5);
  font-weight: 600;
  letter-spacing: var(--track-tight);
  color: var(--ink);
  margin: 0;
}
.reqpage__back--done { margin: var(--s-4) 0 0; }

/* THE SELECT ANSWERS A PRESS, like every other control in the product. It did
   not, and scripts/press_check.mjs did not catch it because the gate had never
   been pointed at this page. --press-light and the shared curve, which is what
   editorial-frame.css gives the desks' own selects: a select is a secondary
   control, never the one loud action on the page. */
.reqfield__input--select:active:not(:disabled) {
  transform: scale(var(--press-light));
  transition: border-color 0.2s ease, transform var(--press-in) var(--press-ease);
}

@media (prefers-reduced-motion: reduce) {
  .reqpage__submit { transition: none; }
  .reqpage__submit:active { transform: none; }
  .reqfield__input--select { transition: none; }
  .reqfield__input--select:active { transform: none; }
}


/* ============================================================
   THE MENU, AND EVERYTHING IT IS MADE OF. 2026-09-17.
   ============================================================
   Ritu's wish list, in his words: "I want a hamburger icon on the opening page
   of the app that will lead to the crucial information from the desktop
   version", then "the login details of the person and a logout option.
   Basically, the full monty, as every professional app should have", then, of
   two screens of the Times of India app, "Follow the standard template for
   professional apps."

   FOUR RULINGS GOVERN WHAT IS BELOW.
   1. It opens only from this page. "This screen should be visible only if
      somebody presses the hamburger icon on the main landing page." The mark
      is on this masthead and on no desk's.
   2. Straight to the list. "There's no need to say 'seven desks' all over
      again."
   3. The mark goes right. "The hamburger icon shouldn't be on the left because
      I feel it will be a tad crowded." Measured, and he is right: this bar
      already carries a 111px wordmark, a rule and a descriptor that wants
      440px, which is why SMART WRITING WORKFLOWS breaks to two lines here.
   4. Every row opens a page. "The moment somebody presses the FAQ section, it
      will go to a page because that will need space." All six do; they are
      generated into help/ from shell/help/ and styled by help.css.

   The drawer's shape is the sample's; its materials are this product's. No
   cards, no dark ground, no mark on every row. */

/* ── THE MARK IN THE MASTHEAD ──────────────────────────────────────────────
   The door head is `justify-content: space-between` with ONE child, so the
   right half of the bar has been empty since it was built. The mark goes
   there. It takes no word and no pill: the three lines are a mark everyone
   reads, so a label beside it would be the second time the same thing is said.

   THE TAP TARGET IS 44px AND THE MARK IS NOT CENTRED IN IT. Measured trap,
   2026-08-20 and again 08-25: a 20px glyph centred in a 44px button leaves the
   mark 12px short of the column the wordmark sits on, and the bar reads
   lopsided while every number in it is correct. The negative margin pulls the
   BOX out to the screen edge so the MARK lands on the column. */
.door-menu {
  /* NO `display` AND NO `place-items` HERE, AND THAT IS THE REPAIR.
     `button:has(> .ico)` in icons.css already makes every marked button
     inline-flex and centres it vertically, and at (0,1,1) it beats a bare
     class. The first version of this rule asked for grid and got flex, so
     `place-items: center` set a justify-items that flex ignores, the mark
     packed to the left edge, and the button shrank to its content.

     MEASURED at 390 before the fix: box 36px wide against the 44 declared,
     mark at x=341.31 in a box starting at 341, its centre 8.10px left of the
     box's. scripts/phone_balance_check.mjs reported it on all four screens,
     which is exactly the finding that gate was written for. */
  width: var(--touch-min);
  height: var(--touch-min);
  margin-right: -12px;
  flex: none;                       /* the door head is a flex row; do not shrink */
  justify-content: center;          /* the axis icons.css leaves to the caller */
  background: none;
  border: 0;
  padding: 0;
  color: var(--ink);
  cursor: pointer;
  border-radius: var(--pill);
}
.door-menu svg { width: 20px; height: 20px; display: block; }
@media (hover: hover) { .door-menu:hover { color: var(--accent); } }
.door-menu:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── THE DRAWER ───────────────────────────────────────────────────────────
   The owner, 2026-09-17, sending two screens of the Times of India app:
   "Follow the standard template for professional apps."

   WHAT THE SAMPLE SETTLES IS THE GEOMETRY, NOT THE LOOK. A drawer that covers
   part of the screen over a dimmed page, identity at the top of it, the
   utility actions pinned at its foot. The first build here was a full-screen
   sheet, which loses the one thing the sample gets right: you can still see
   the page you came from, so it is obvious you are in something you opened
   and can close.

   WHAT IS NOT TAKEN: the cards, the dark ground and the icon on every row.
   This product does not use cards (app-door.css says so in as many words), the
   door is daylight by ruling, and a mark on every row is the pattern the
   design skill names as the lazy container. The sample settles the shape of
   the thing; the shipped design system settles what it is made of.

   IT COMES FROM THE RIGHT because its control is on the right. The sample's
   hamburger is top-left and its drawer is left; ours cannot be, because the
   wordmark owns the left of this masthead. A panel that arrives from the side
   opposite its own button is the thing that feels wrong without being able to
   say why. */
.menu-scrim {
  position: fixed;
  inset: 60px 0 0 0;
  z-index: 30;
  background: var(--overlay-bg);
}
.menu-sheet {
  position: fixed;
  top: 60px;
  right: 0;
  bottom: 0;
  /* 360 is the ceiling so the drawer never becomes the whole screen on a
     tablet; 86vw keeps a strip of the door in view on the narrowest phone.
     Measured at 358, his own zoomed width: 308px of drawer, 50px of page. */
  width: min(86vw, 360px);
  z-index: 31;
  background: var(--paper);
  border-left: var(--hairline) solid var(--rule);
  box-shadow: -8px 0 28px rgba(20, 17, 17, .14);
  display: flex;
  flex-direction: column;
}
/* Only the middle scrolls. The identity stays at the top and the actions stay
   at the foot, which is the half of the sample worth keeping: the two things
   you might need are never scrolled off. */
.menu-scroll { flex: 1; overflow-y: auto; overscroll-behavior: contain; }

/* ── THE ROWS ──────────────────────────────────────────────────────────────
   The door's own grammar, deliberately: a row with a hairline under it, the
   name in the display face and the line under it in the reading serif. Not
   cards, because this product does not use cards, and not the coloured bands
   either — a band means "a room of desks", and none of these is a desk. */
.menu-list { display: grid; gap: 0; }
.menu-item {
  display: grid;
  gap: var(--s-1);
  padding-block: var(--s-3);
  padding-inline: calc((100% - var(--frame)) / 2);
  border-top: var(--hairline) solid var(--rule-soft);
  min-height: var(--touch-min);
  align-content: center;
  text-decoration: none;
  color: inherit;
  transition: background 160ms var(--ease);
}
/* A ROW THAT IS A BUTTON LOOKS LIKE EVERY OTHER ROW. Sign out posts, so it is
   a <button>, and a button arrives with a background, a border, a centred line
   and the platform's own font. These four lines take all of that off; nothing
   here is a new style, it is the browser's defaults being cleared so the rule
   above can apply to both elements. */
button.menu-item {
  width: 100%;
  font: inherit;
  text-align: left;
  background: none;
  border: 0;
  border-top: var(--hairline) solid var(--rule-soft);
  cursor: pointer;
}
button.menu-item[disabled] { cursor: default; }
/* The refusal, in the tone the sign-in screen says its own. */
.menu-note {
  margin: 0;
  padding-block: var(--s-2) var(--s-3);
  padding-inline: calc((100% - var(--frame)) / 2);
  font-family: var(--font-body);
  font-size: var(--t-2);
  color: var(--quiet);
}
.menu-list > .menu-item:first-child { border-top: 0; }
/* NO RULE UNDER THE LAST ROW. With one, the list closed with a hairline, then
   40px of nothing, then the account group opened with a second hairline, and
   the pair read as an empty row somebody forgot to fill. The gap is the
   division; one rule under it is enough. */
.menu-name {
  font-family: var(--font-display);
  font-variation-settings: "wght" 700;
  font-size: var(--t-4);
  letter-spacing: -0.01em;
  color: var(--ink);
}
.menu-what {
  font-family: var(--font-body);
  font-size: var(--t-2);
  line-height: 1.45;
  color: var(--quiet);
  max-width: 34ch;
}
@media (hover: hover) {
  .menu-item:hover { background: var(--paper-warm); }
  .menu-item:hover .menu-name { color: var(--accent); }
}
.menu-item:active { transform: scale(var(--press-light)); }
.menu-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

/* ── THE ACCOUNT ───────────────────────────────────────────────────────────
   On its own ground, because it is the one block on this screen that is about
   the reader rather than about the product. --paper-deep is the sheet's own
   second ground; it is not a card and it reaches both edges of the screen. */
.menu-account {
  margin-top: var(--s-5);
  border-top: var(--hairline) solid var(--rule);
  /* A foot of its own. Without it the deep ground stopped dead on the
     last row's rule and the block read as cut off rather than closed. */
  padding-block: var(--s-4) var(--s-5);
}
.menu-who { padding-inline: calc((100% - var(--frame)) / 2); }
.menu-who-name {
  margin: 0;
  font-family: var(--font-display);
  font-variation-settings: "wght" 700;
  font-size: var(--t-5);
  letter-spacing: -0.02em;
  color: var(--ink);
}
.menu-who-mail {
  margin: var(--s-1) 0 var(--s-4);
  font-family: var(--font-mono);
  font-size: var(--t-1);
  color: var(--quiet);
  word-break: break-all;
}
.menu-account .menu-item { border-top-color: var(--rule); }
.menu-account .menu-item:last-child { border-bottom: var(--hairline) solid var(--rule); }
/* The count is a fact about the row, so it sits on the row rather than under
   it in the voice the descriptions use. */
.menu-item--count { grid-template-columns: 1fr auto; align-items: baseline; }
.menu-count {
  font-family: var(--font-mono);
  font-size: var(--t-2);
  color: var(--quiet);
}
/* Signing out everywhere is the bigger of the two and says so by being the
   quieter: the ordinary action keeps full ink, the one you reach for after
   losing a phone is set smaller and named in full. */
.menu-item--quiet .menu-name {
  font-variation-settings: "wght" 500;
  font-size: var(--t-3);
  color: var(--ink-soft);
}


/* ── WHO YOU ARE, AT THE TOP ───────────────────────────────────────────────
   The owner, 2026-09-17: "Follow the standard template for professional apps."
   That template is not a preference, it is what every drawer the reader
   already uses does: identity at the top carrying no action, navigation under
   it, the account actions at the foot. Signing out is not something anyone
   should reach for on the way in.

   The face is initials on the brand teal until Google's own picture is stored.
   That claim is returned at sign-in and simply is not kept today, so the
   initials are the honest state and not a placeholder for the picture. */
.who {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  gap: var(--s-3);
  padding-block: var(--s-4);
  padding-inline: calc((100% - var(--frame)) / 2);
  background: var(--paper-deep);
  border-bottom: var(--hairline) solid var(--rule);
}
.who-face {
  width: 44px;
  height: 44px;
  border-radius: var(--pill);
  background: var(--accent);
  color: var(--paper-warm);
  display: grid;
  place-items: center;
  font-family: var(--font-display);
  font-variation-settings: "wght" 600;
  font-size: var(--t-3);
  letter-spacing: 0.02em;
}
.who-text { display: grid; gap: 2px; min-width: 0; }

/* The owner, 2026-09-17: "the login details should feature the Google icon to
   the right of the name, if the user has one." Beside the NAME, not out at the
   far edge of the row: it is a fact about that name, and a mark stranded on
   the other side of the screen reads as a control you can press.

   Google's own mark, unaltered and in full colour, which is what their brand
   terms require and what the sign-in button on the door already uses.

   `if the user has one` is the whole rule: it renders only for an account that
   signed in through Google. There is one way in today, so it is always there
   today; the day a second way exists, an account that did not use Google shows
   no mark rather than a wrong one. */
.who-name {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  font-family: var(--font-display);
  font-variation-settings: "wght" 700;
  font-size: var(--t-4);
  letter-spacing: -0.01em;
  color: var(--ink);
  min-width: 0;
}
/* Cap-height, not baseline: a square mark has no baseline of its own and
   drops below the letters it stands beside if it is asked to share one. */
.who-idp { display: block; line-height: 0; flex: none; transform: translateY(1px); }
.who-idp svg { display: block; }
.who-mail {
  font-family: var(--font-mono);
  font-size: var(--t-1);
  color: var(--quiet);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── THE PINNED FOOT ───────────────────────────────────────────────────────
   Two rows, always on screen, at the end the thumb reaches last. Signing out
   is not something to meet on the way in.

   `Sign out everywhere` is NOT here. It belongs on the devices screen, which
   is where somebody goes when a phone has gone missing, and pinning a third
   row would make the foot taller than the list it serves. */
.menu-foot {
  border-top: var(--hairline) solid var(--rule);
  background: var(--paper-deep);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.menu-foot .menu-item { border-top: 0; }
.menu-foot .menu-item + .menu-item { border-top: var(--hairline) solid var(--rule); }

/* WHICH OF THE TWO MARKS SHOWS. Both are in the button from the start, so a
   press swaps a class rather than rebuilding an svg, and the icon set's own
   markup is never rewritten by hand at runtime. The button is a grid with one
   cell, so the two marks stack in the same place and the bar cannot shift by a
   pixel as they change. */
/* KEYED ON data-menu, NOT ON A SECOND CLASS, and that is not a style choice.
   scripts/fill_icons.py matches `<span class="ico" data-icon="...">` with the
   class exactly, so a mark carrying a second class is invisible to the filler
   and ships as an empty span. Measured: the first version of these two marks
   filled in 0 files. */
.door-menu .ico { font-size: 20px; }
.door-menu .ico[data-menu="close"] { display: none; }
.door-menu[aria-expanded="true"] .ico[data-menu="open"] { display: none; }
.door-menu[aria-expanded="true"] .ico[data-menu="close"] { display: block; }

/* The mark takes the masthead's own ink, not the set's default teal. Every
   other control in this bar is drawn in --ink, and one teal mark among them
   would read as the only live thing on the bar. */
.door-menu { --ico-color: currentColor; }
