/* ===========================================================================
   THE LIFE ROOM — the room's own rules.
   ===========================================================================
   Moved out of app.css (rebuild, 15 Oct). Loaded right after it.

   STAYS IN app.css because other pages use it: the --life-heat-*, --life-wall-*,
   --life-piece and --la-* tokens (stated per theme in the token blocks), the
   nav row's colour (.nav-item[data-group="life"]), the Feed's .feed-life-*
   rules, and the shared .lucid-door base.

   WHAT THE FOLD DID. Every wall carries data-hang (quiet or full) and every
   piece carries data-tier, and the compact-row rules that came last in the old
   file overrode the whole of the tier ladder, the 1100px re-hang, the quiet
   re-hang, the old 12-column wall and the old door tracks, property by
   property. The rules below are what was left standing once those were
   removed: one rule per selector. The unrendered .life-chip / .life-photo /
   data-hang="strip" blocks are gone with them — nothing draws those classes.
   tests/test-room-life-unchanged.js holds the result to the old pixels.
   =========================================================================== */

/* =====================================================================
   THE LIFE WALL — treatment H, chosen after two rounds of demos.
   ---------------------------------------------------------------------
   Round one's five Life treatments were one object in five hats:
   fourteen-pixel rows, a date on the left, a category in small capitals
   on the right. Matt: "They all look like a spreadsheet. Where is the
   design? Where is the creativity? Boring static, lifeless life page."
   He was right; round one optimised for honesty and produced a database
   receipt that printed its own source table under every row.

   THE WALL IS THE ANSWER TO THAT SPECIFIC FAILURE, and it is three
   statements made at once about the same list:

       SIZE     is consequence,
       COLOUR   is how far off a thing is,
       POSITION is the same — hot and large toward the upper left, cool
                and small toward the lower right, along the heat rule
                that runs under the masthead.

   THE GRID IS AUTO-FLOW DENSE RATHER THAN A MAP OF NAMED AREAS, and
   that is the one real departure from tools/life-demo/H-wall.html. The
   mockup could name thirteen slots because it knew there would be
   thirteen pieces; a real wall is hung out of whatever the merge came
   back with, which is between nought and thirteen and a different set
   every day. Spans plus `dense` give the same composition from a list
   of unknown length: the anchor takes half the top band, the second
   piece takes the rest of its height, and the two small pieces that
   follow drop into the column beside them because dense is allowed to
   reach forward for something that fits. That is not an approximation
   of the mockup's top band — it is the mockup's top band.

   FIVE TIERS, NOT THIRTEEN SLOTS. Rank comes from consequence and the
   tier comes from rank, so the biggest piece is the most consequential
   thing available rather than whatever happens to be first. On a day
   with nothing urgent that is a body or a goal instead of an emergency,
   which is the whole argument of the quiet state below. */
/* 12 SEPTEMBER — THIS SAID var(--radius-pill) AND THE WHOLE PAGE WAS AN
   ELLIPSE. The radii sweep (commit 9565af7) mapped every 20px corner in the
   stylesheet onto --radius-pill, which is 999px. On a 24px chip a 20px
   radius IS a capsule and the mapping is right; on a 1290x1482 PANEL it
   clips the page to an oval, and because .life-page also carries
   overflow:hidden it took the title, the axis labels, the left-hand column
   of the wall and half the doors with it. Matt, seeing it: "D is a mess."
   The mess was not the doors — it was this line, and it had been shipping
   since the sweep. 20px belonged on --radius-card, which is the token for a
   panel; tools/audit-the-pills.js now walks every --radius-pill in the file
   and fails on any element too big to be a capsule. */
.life-page{ position:relative; padding:var(--space-6) var(--space-6) var(--space-8); border-radius:var(--radius-card);
  border:1px solid var(--glass-border); overflow:hidden;
  /* One warm pool where the hot corner is, two of the area's own green
     elsewhere, over the app's vertical gradient. The warm pool is doing
     work rather than decoration: it sits exactly where the layout puts
     the nearest thing, so the ground itself leans the eye to where the
     reading starts. */
  background:
    radial-gradient(820px 560px at 13% -8%, color-mix(in srgb, var(--cat-orange-2) 9%, transparent), transparent 62%),
    radial-gradient(940px 720px at 97% 34%, color-mix(in srgb, var(--area-life) 7%, transparent), transparent 60%),
    radial-gradient(720px 580px at 6% 110%, color-mix(in srgb, var(--area-life) 5%, transparent), transparent 60%),
    linear-gradient(178deg, var(--life-wall-1), var(--life-wall-2) 52%, var(--bg-deep)); }

/* ---- the masthead and the heat rule --------------------------------
   THE LEGEND IS DRAWN RATHER THAN LISTED. A gradient running the full
   width from the alarm colour to the standing colour, with its six
   bands named underneath. It teaches the colour in one glance, and it
   is also the wall's horizontal axis: pieces hang left to right in
   roughly the order this rule runs, so the legend and the layout are
   the same statement made twice. */
.life-mast{ display:flex; align-items:flex-end; justify-content:space-between;
  gap:var(--space-6); flex-wrap:wrap; }
.life-said{ font-family:var(--font-display); font-size:var(--type-lead); line-height:1.45;
  color:var(--text-dim); max-width:520px; text-align:right; margin:0; }
.life-said b{ color:var(--text); font-weight:600; }
.life-heat{ margin:var(--space-4) 0 var(--space-5); }
.life-heat-bar{ height:3px; border-radius:var(--radius-tight); opacity:.9;
  background:linear-gradient(90deg, var(--life-heat-0), var(--life-heat-1) 20%,
    var(--life-heat-2) 42%, var(--life-heat-3) 64%, var(--life-heat-4) 84%, var(--life-heat-5)); }
.life-heat-keys{ display:flex; justify-content:space-between; margin-top:var(--space-2);
  font-family:var(--font-mono); font-size:var(--type-label);
  letter-spacing:.13em; text-transform:uppercase; }
/* THE KEY IS DIMMED WHEN THE BAND IS EMPTY, and that is a fact rather
   than a flourish: the merge says which bands have anything in them, so
   a column heading with nothing under it says so instead of implying a
   piece is missing from beneath it. */
.life-heat-keys span{ color:var(--text-faint); }
.life-heat-keys span.on:nth-child(1){ color:var(--life-heat-0); }
.life-heat-keys span.on:nth-child(2){ color:var(--life-heat-1); }
.life-heat-keys span.on:nth-child(3){ color:var(--life-heat-2); }
.life-heat-keys span.on:nth-child(4){ color:var(--life-heat-3); }
.life-heat-keys span.on:nth-child(5){ color:var(--life-heat-4); }
.life-heat-keys span.on:nth-child(6){ color:var(--life-heat-5); }
.life-heat-keys span:not(.on){ opacity:.42; }

/* ---- the wall ------------------------------------------------------ */
.life-wall{ display:grid; grid-template-columns:repeat(5, minmax(0,1fr));
  grid-auto-rows:minmax(120px, auto); grid-auto-flow:row dense; gap:var(--space-3); }
/* THE LIFE WALL, AS ONE COMPACT ROW OF TILES.

   "In the life area, the KPI tiles are obnoxious. All 5 tiles
    should fit on a row below timeline."   (Matt.)

   THE TIMELINE IS THE HEAT RULE (already gone ... no date), THE
   TILES ARE THE PIECES HUNG UNDER IT. They were a composition of
   tall portraits and wide landscapes up to 400px high, each with
   a 74px figure on it. This is the opposite decision, and it
   replaces the old tier sizes rather than sitting beside them:
   EVERY PIECE IS THE SAME SIZE, FIVE ACROSS, AND A DAY WITH
   FIVE THINGS ON IT IS ONE ROW. More than five wrap onto further
   rows of five; fewer simply leave the row short. 3 + 2 below
   1100, two across below 560.

   Same figure, same title, same line of small print, same
   drawing: nothing was taken off a piece, it was only set in
   the type the rest of the app uses for a tile, with the
   drawing kept as a strip above it. The rows are `auto` with a
   floor rather than a fixed height, so a caption that needs a
   second line makes its row taller instead of being clipped,
   and every tile in a row stays the same height as its
   neighbours. The failed-read piece names every missing drawer
   in a list, so it takes the whole row instead of being
   squeezed into a fifth of one.

   The twelve-column wall with its five tiers of span, and the quiet wall's
   re-hung tiers, that stood here are gone: this replaced them property by
   property. THE WALL IS RE-HUNG, NOT REFLOWED applies still — it is the same
   pieces in fewer columns. */
@media (max-width: 1100px){
  .life-wall{ grid-template-columns:repeat(3, minmax(0,1fr)); }
}
@media (max-width: 560px){
  .life-wall{ grid-template-columns:repeat(2, minmax(0,1fr)); }
}

/* ---- a piece -------------------------------------------------------
   A PIECE IS A PICTURE WITH A CAPTION UNDER IT, and the two never share
   the same square inch. Laying the type over the drawing the way a
   magazine spread does fails here for a reason that no amount of scrim
   fixes: the interesting part of a drawing is precisely where the
   interesting part of a sentence wants to be. So every piece is a
   vertical split — the art takes whatever height is left once the
   caption has had what it needs — which is also how a framed print on a
   real wall works.

   --h is the piece's heat colour and it does three things at once: it
   tints the ground, it colours the number, and it lights the hairline
   on hover. One variable, three signals, and no badge anywhere. */
.life-piece{ position:relative; overflow:hidden; border-radius:var(--radius);
  border:1px solid var(--glass-border); background:var(--life-piece);
  box-shadow:var(--glass-shadow); padding:0; margin:0;
  display:flex; flex-direction:column; width:100%; text-align:left;
  grid-column:span 1; grid-row:span 1; min-height:120px; min-width:0;
  font:inherit; color:inherit; cursor:pointer;
  transition:border-color var(--dur-base) var(--ease-out),
             transform var(--dur-base) var(--ease-out); }
.life-piece::before{ content:""; position:absolute; inset:0; pointer-events:none; z-index:0;
  background:radial-gradient(130% 110% at 6% 0%,
    color-mix(in srgb, var(--h) 15%, transparent), transparent 66%); }
.life-piece:hover{ transform:translateY(-1px);
  border-color:color-mix(in srgb, var(--h) 60%, transparent); }
.life-piece:focus-visible{ outline:2px solid var(--h); outline-offset:3px; }
/* THE MOUNT. A drawn piece needs a margin for the same reason a print
   does: without one the line work runs into the frame and the wall
   reads as noise rather than as a set of pictures. */
.life-frame{ position:relative; z-index:1; flex:1 1 40px; min-height:40px;
  padding:var(--space-2) var(--space-3) 0; }
.life-art{ width:100%; height:100%; display:block; }
.life-art text{ font-family:var(--font-mono); }

/* FILM GRAIN, AND IT IS THE CHEAPEST THING ON THIS PAGE ON PURPOSE.
   A fine, even grain over a drawing is the one texture cue that
   separates a premium interface from a clip-art one: it puts every
   drawing on the same stock and stops a gradient banding on a wide
   mount.

   THE OBVIOUS WAY TO GET IT IS AN <feTurbulence> AND THAT WAY IS SHUT.
   The note beside the dust in app.js records what happened the last
   time this page asked a software renderer for a masked, patterned,
   partially transparent rectangle at three hundred pixels: the
   screenshot never came back. Thirteen turbulence filters is the same
   bill again. Three tiled radial gradients at coprime sizes give a
   speckle with no visible repeat, they rasterise as a tile once and
   tile for free, and they cost the compositor nothing.

   THE BLEND MODE IS A TOKEN because overlay grain on paper silvers the
   sheet and multiply grain on black disappears — which is the same
   day-and-night split every other --la-* token here is making. */
.life-frame::after{ content:""; position:absolute; inset:0; pointer-events:none; z-index:3;
  opacity:var(--la-grain-o); mix-blend-mode:var(--la-grain-blend);
  background-image:
    radial-gradient(circle at 21% 33%, var(--la-core) 0 .35px, transparent .6px),
    radial-gradient(circle at 67% 11%, var(--la-core) 0 .3px, transparent .55px),
    radial-gradient(circle at 44% 78%, var(--la-core) 0 .32px, transparent .58px);
  background-size:3px 3px, 4px 4px, 5px 5px; }

/* DEPTH OF FIELD, AND THE ONLY REAL BLUR ANYWHERE ON THE WALL.
   The plane behind the subject — the survey grid under a route, the
   receding lines under a horizon, the arc a pendulum has already
   travelled — is genuinely out of focus rather than merely faint, which
   is what puts the subject in FRONT of its background instead of on it.
   One CSS filter on one group is a compositor job done once on the GPU;
   the same effect asked for as an SVG filter primitive is a raster of
   the whole drawing, per piece, on the CPU. */
.life-art .la-far{ filter:blur(2.4px); }
@media (prefers-reduced-transparency: reduce){ .life-art .la-far{ filter:none; } }
.life-lab{ position:relative; z-index:2; flex:0 0 auto; display:flex; flex-direction:column;
  padding:var(--space-1) var(--space-3) var(--space-2); }
.life-row{ display:flex; align-items:baseline; gap:var(--space-2); flex-wrap:wrap; }
/* ONE SIZE OF TYPE FOR EVERY PIECE. The wall used to be five tiers with five
   sizes of figure, title and caption (a 74px anchor down to a 25px stamp) and a
   re-hung quiet wall with larger tails; the compact row replaced all of them
   with the type the rest of the app uses for a tile, the drawing kept as a
   strip above it. A caption that needs a second line makes its row taller
   instead of being clipped. */
.life-num{ font-family:var(--font-display); font-weight:600; font-size:var(--type-page);
  color:var(--h); line-height:.9; letter-spacing:-.03em;
  font-variant-numeric:lining-nums tabular-nums; }
.life-unit{ font-family:var(--font-mono); font-size:var(--type-label);
  letter-spacing:.08em; text-transform:uppercase; color:var(--h); opacity:.92; }
.life-ttl{ font-family:var(--font-display); font-weight:600; font-size:var(--type-body); color:var(--text);
  margin:var(--space-1) 0 0; letter-spacing:-.008em; line-height:1.24; overflow-wrap:break-word; }
.life-sub{ color:var(--text-dim); margin:var(--space-1) 0 0; line-height:1.48;
  font-size:var(--type-label); overflow-wrap:break-word; }
.life-ftn{ color:var(--text-faint); margin:var(--space-1) 0 0; line-height:1.42;
  font-size:var(--type-label); overflow-wrap:break-word; }

/* ---- MOTION --------------------------------------------------------
   TWO THINGS MOVE ON THIS WALL AND BOTH MEAN SOMETHING.

   THE LIGHT ON THE ROUTE. A short bright dash travels from here to
   there once every seven seconds. It means the thing is coming toward
   him, and it exists only on the piece that is a journey.

   THE BREATH ON THE OPEN THING. Anything already gone carries a slow
   bloom in the gap of its own cover. It is the one piece where nothing
   is counting down, because nothing is going to fix itself, and a thing
   that is still open should look like it is still open.

   Both are off under prefers-reduced-motion: the travelling light parks
   at its destination and the bloom sits at its middle value, so a
   reader who asks for stillness loses the movement and not the meaning.
   Both are a transform-free stroke/opacity animation on one small
   element, which is what keeps thirteen of them cheap. */
.life-travel{ stroke-dasharray:26 2000; stroke-dashoffset:640;
  animation:lifeTravel 7s linear infinite; }
@keyframes lifeTravel{ from{ stroke-dashoffset:640; } to{ stroke-dashoffset:-30; } }
.life-breath{ animation:lifeBreath 5s var(--ease-out) infinite; }
@keyframes lifeBreath{ 0%,100%{ opacity:.14; } 50%{ opacity:.46; } }
@media (prefers-reduced-motion: reduce){
  .life-travel{ animation:none; stroke-dashoffset:0; }
  .life-breath{ animation:none; opacity:.3; }
}

/* ---- first run, and the wall that could not be read -----------------
   A GHOST FRAME IS NOT A PLACEHOLDER CARD. It is the piece that will
   hang there, drawn at a tenth, with a picture hook above it and one
   true sentence about what would fill it. The point is that the shape
   of the page exists before the data does, so an empty Life is a wall
   waiting to be hung rather than seven invitations in a form. */
.life-piece.ghost{ background:transparent; box-shadow:none; cursor:default;
  border:1px dashed color-mix(in srgb, var(--text-faint) 32%, transparent); }
.life-piece.ghost::before{ display:none; }
/* A TENTH IS WHAT THE MOCKUP USED AND IT WAS TOO LITTLE HERE. Its ghosts
   were dense drawings — twenty-four book spines, a bar in eight steps — and
   these are lighter line work, so at a tenth the frames came out empty and
   the argument of the state (this is the piece that will hang here, at the
   size it will be) was lost. */
.life-piece.ghost .life-art{ opacity:.17; }
.life-piece.ghost .life-ttl{ color:var(--text-dim); }
.life-hook{ position:absolute; top:10px; left:50%; transform:translateX(-50%); z-index:3; }
/* The one live frame on an empty wall carries the only thing that is
   true on day one — two of the seven cabinets can fill themselves out
   of his mail and five cannot — and its picture is drawn back to a
   third, because a full-strength route on a wall with no trips on it is
   a page showing somebody a journey they have not got. */
.life-piece.live{ cursor:default;
  border-color:color-mix(in srgb, var(--brass) 50%, transparent); }
.life-piece.live .life-art{ opacity:.30; }
.life-cta{ display:inline-flex; align-items:center; align-self:flex-start;
  margin-top:var(--space-4); font-size:var(--type-body); font-weight:650; font-family:inherit;
  /* The ink is the page's own floor colour, which is the one token that
     is guaranteed to be the opposite polarity to --brass in every theme:
     cream brass on a near-black floor in the dark themes, and a dark
     olive brass on cream in the light ones. A literal #12100a would have
     been unreadable on five of the twelve. */
  color:var(--bg-deep); background:var(--brass);
  border:0; border-radius:var(--radius-control); padding:var(--space-2) var(--space-4); cursor:pointer; }
.life-cta:hover{ background:color-mix(in srgb, var(--brass) 84%, var(--text)); }
.life-cta:focus-visible{ outline:2px solid var(--ring); outline-offset:2px; }
:root:not([data-mode="light"]) .life-cta{ color:var(--on-brass); }

/* THE READ THAT FAILED, WHICH IS NOT A SHORTER WALL. A partial merge
   hangs what it got AND says which cabinet is missing, in the loudest
   slot on the page, because a wall that quietly leaves out Vault looks
   exactly like a man with nothing in his vault. */
.life-piece.broken{ cursor:default; grid-column:1 / -1;
  border-color:color-mix(in srgb, var(--life-heat-0) 45%, transparent); }
.life-broke-list{ margin:var(--space-2) 0 0; padding:0; list-style:none;
  font-size:var(--type-body); line-height:1.5; color:var(--text-dim); }
.life-broke-list li{ padding:var(--space-1) 0 var(--space-1) var(--space-3); position:relative; }
.life-broke-list li::before{ content:"—"; position:absolute; left:0;
  color:var(--life-heat-0); }
.life-broke-list b{ color:var(--text); font-weight:600; }

/* ---- the seven doors -----------------------------------------------
   Item 620, and the reason this row is part of the contract rather than
   furniture: giving the Life group a `page` in NAV_GROUPS takes its
   dropdown away, so without these seven cards all seven children have
   no route from the page that replaced their menu.

   THEY ARE CARDS, NOT CHIPS. That was the whole of his complaint about
   Work, and the same tiny .ht-verb strip would have been the same
   mistake here. The shape is Work's, stated as one rule for both rather
   than copied, so the two rows cannot drift apart; only the colour of
   the count differs, and it differs because it is a fact about a
   different area.

   WHY THEY SIT UNDER THE WALL AND NOT ABOVE IT. The argument for giving
   an area a page at all was that "what a person wants on arriving is
   the answer rather than a list of the rooms the answer might be in" —
   so seven navigation cards above the wall would rebuild, in cards, the
   menu that was deleted. They go under it, and they are not the only
   route: every piece on the wall is itself a door into the cabinet it
   came out of, so this row is the guarantee for the cabinets that have
   nothing hanging today rather than the only way in. */
/* The tracks are the shared tile-grid ladder (--tile-min, --tile-gap,
   --tile-span), which is what won over the four-across, two-across and
   one-across rules that used to stand here. */
.life-doors{ display:grid; grid-template-columns:repeat(auto-fill, minmax(min(100%, var(--tile-min)), 1fr));
  gap:var(--tile-gap); align-items:start; max-width:var(--tile-span); margin:var(--space-5) 0 0; }
.lucid-door.life-door{ display:flex; flex-direction:column; align-items:flex-start;
  gap:var(--space-1); width:100%; text-align:left; font:inherit; padding:var(--space-3);
  border:1px solid var(--glass-border); border-radius:var(--radius-card);
  background:var(--glass-bg);
  transition:background var(--dur-fast) var(--ease-in-out),
             border-color var(--dur-fast) var(--ease-in-out); }
.lucid-door.life-door:hover{ text-decoration:none;
  border-color:var(--glass-border-hi);
  background:color-mix(in srgb, var(--text-dim) 10%, var(--glass-bg)); }
.lucid-door.life-door:focus-visible{ outline:2px solid var(--ring); outline-offset:2px; }
.life-door-name{ font-size:var(--type-body); font-weight:650; color:var(--text); }
/* min-height, so the slot is held on a door with no number to give.
   Nothing is painted there; what is held is the space, and without it
   the sentence under one door sits a line higher than the sentence
   under its neighbour and the row reads as ragged rather than as seven
   of one thing. */
.life-door-count{ min-height:17px; font-size:var(--type-body); font-weight:600;
  color:var(--area-life); }
.life-door-count.none{ font-weight:500; color:var(--text-faint); }
/* A READ THAT FAILED MUST NOT LOOK LIKE A CUPBOARD WITH NOTHING IN IT.
   Same ink weight as the note above it and a different colour, because
   "Lucid could not look" and "there is nothing there" are the two
   sentences this whole page exists to keep apart. */
.life-door-count.failed{ color:var(--life-heat-0); }
.life-door-what{ font-size:var(--type-body); color:var(--text-dim); line-height:1.45; }

.life-foot{ margin-top:var(--space-5); display:flex; gap:var(--space-6); flex-wrap:wrap;
  align-items:baseline; font-size:var(--type-body); color:var(--text-faint); line-height:1.55; }
.life-foot b{ color:var(--text-dim); font-weight:600; }

/* ---- 1100 -----------------------------------------------------------
   The wall's columns step down with the width (see .life-wall above); the
   masthead sentence moves to the left and the page gives up some padding. */
@media (max-width: 1100px){
  .life-page{ padding:var(--space-5) var(--space-4) var(--space-6); }
  .life-said{ text-align:left; font-size:var(--type-lead); max-width:none; }
}
