/* ==========================================================================
   Application detail — the page-local composition of single-application.php.
   Sources: 网站规划.md §B6/§B8 (the pattern and its evidence) · DESIGN新-V2.md
   §6 grid, §7 image language, §15 cards, §18 process, §20 motion.

   The page is read in modules rather than as prose: an index of boxes under the
   first screen, the use as two columns of words over pictures, the requirements
   as numbered cards, the project as four numbered steps over its photographs,
   and the routes out of the page as boxes with a picture each.

   Everything that is shared stays shared: colours, type and spacing come from
   tokens.css, the vocabulary (.section, .split, .card, .plain, .kicker) from
   style.css. Nothing here states a colour of its own.
   ========================================================================== */

/* ---------- The first screen's photograph ----------------------------------
   The picture is a square and it stands as the second half of the first screen: it
   takes the width of the column it stands in, held to a cap that is the band's own
   height, so a square large enough to answer the words never pushes the buttons
   below the fold on a short screen. Held to a fixed 420px instead, the picture read
   as a small card floating in a wide empty area beside the words and the two halves
   of the first screen did not answer each other. The frame itself is unchanged — it
   crops by object-fit, as every frame on the site does, and the square is the ratio
   single-application.php gives it, so a photograph of any shape lands in it. */
.app-hero__figure{width:min(100%,560px,58vh);max-width:none;margin-inline:auto}

/* ---------- A frame with no caption on it ----------------------------------
   components/media.php prints the slot's pixel size over the frame so a
   photograph can replace it one by one. These frames are all the same square and
   are named by the picture inside them, so the badge has nothing to add: it goes
   on the real photograph and on the empty slot alike. No frame on this page
   prints a caption — only the record's own words do. */
.app-shot .ph>span{display:none}
.app-set .fig,.app-tiles .fig{gap:0}

/* ---------- The use: two columns of words over pictures --------------------
   Each column is symmetric — the record's paragraph at the top, two square
   frames under it — so the section reads as two halves of one use rather than as
   prose with a picture beside it. Two shared rows — words, then pictures — are
   what the columns are built on: subgrid keeps each column's pictures level with
   the other's even when one paragraph runs longer. The columns carry one
   paragraph each; a body of one paragraph stays one column, at a readable
   measure, instead of leaving half the row empty. */
.app-use{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:auto auto;
  row-gap:clamp(18px,2vw,26px);column-gap:clamp(24px,4vw,58px);align-items:start}
/* The heading keeps the 12–14px of air style.css gives a lede (.sec-head .lede),
   not the 30–48px a section head keeps before a grid of cards: what follows it here
   is the sentence that continues the heading, not a new block. */
.app-use-section .sec-head{margin-bottom:clamp(12px,1vw,14px)}
.app-use--1{grid-template-columns:minmax(0,720px);justify-content:center}
.app-use__col{display:grid;grid-template-rows:subgrid;grid-row:span 2;
  gap:clamp(16px,2vw,24px);align-content:start;min-width:0}
.app-use__text{max-width:60ch;min-width:0}
.app-use__text p{font-size:clamp(15.5px,1.1vw,17px);line-height:1.65}
.app-use__text p:last-child{margin-bottom:0}
.app-tiles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(10px,1.2vw,16px);align-content:start;margin:0}
@media (max-width:900px){
  .app-use{grid-template-columns:1fr}
}

/* ---------- Requirements as cards ------------------------------------------
   Four items with the number carrying the accent, so the list reads in order
   without a line of prose introducing it. */
.app-req__item{gap:8px}
.app-req__item .kicker{color:var(--accent)}
.app-req__item h3{font-size:17px}

/* ---------- The project's account ------------------------------------------
   Four steps in two rows of two: a number, a name and what happened, each on a
   readable measure and separated by a hairline rather than boxed, so the account
   does not read as a second row of cards under the requirements. */
.app-narr{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(22px,3vw,38px) clamp(26px,4vw,60px)}
.app-narr__item{border-top:1px solid var(--line);padding-top:clamp(14px,1.6vw,20px);min-width:0}
.app-narr__item .kicker{color:var(--accent);margin-bottom:6px}
.app-narr__item h3{margin:0 0 8px;font-size:clamp(17px,1.5vw,20px)}
.app-narr__item p:not(.kicker){max-width:56ch;margin:0;font-size:15px;line-height:1.65;color:var(--muted)}
@media (max-width:860px){
  .app-narr{grid-template-columns:1fr}
}

/* ---------- The project's photographs --------------------------------------
   A row of four equal square frames under the account: a square takes a product
   photograph, a scene or a carton without cutting more out of one than another
   (the same frame the site's customization sequence uses). Four across on a wide
   screen, two on a tablet, and a real photograph fills a frame as it arrives. */
.app-set{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  gap:clamp(12px,1.4vw,18px);align-items:start}

/* ---------- The two routes out of the project ------------------------------
   One column per kind of route: the capabilities the project used, the categories
   a buyer can start from. No panel is drawn around either list — one vertical rule
   holds the columns apart, with a gutter on each side of it, and the kicker over
   each list is its only heading. Inside a column every route keeps a small white
   frame of its own, its words on the left and the picture it leads to on the
   right, so the column reads as a stack of routes rather than as a form. */
.app-box{padding:0;border:0;background:none}
.app-box .kicker{margin-bottom:8px}
.app-box__list{list-style:none;margin:0;padding:0;display:grid;gap:10px}
.app-box__list li{margin:0;font-weight:600}
/* One size for every route in a column: the capability link and the category link
   carry different shared classes, so the column states the size for both. */
.app-box__list a{font-size:15px}
/* Each route stands in a frame of its own inside the column: the words on one side,
   the picture the route leads to on the other. The frame is white on a hairline, so
   a route reads as a card rather than as part of a panel; pointed at, the line
   deepens and the frame lifts a little. */
.app-box__list li.app-box__row{display:grid;grid-template-columns:minmax(0,1fr) auto;
  gap:14px;align-items:center;padding:12px 14px;
  border:1px solid var(--line);border-radius:6px;background:var(--white);
  transition:border-color var(--dur-fast) ease,box-shadow var(--dur-fast) ease}
.app-box__list li.app-box__row:hover,
.app-box__list li.app-box__row:focus-within{border-color:var(--line-2);
  box-shadow:var(--shadow-1)}
.app-thumb{width:84px;max-width:84px;margin:0;flex:0 0 auto}
.app-thumb .ph{border-radius:8px;overflow:hidden}
/* No picture for this route yet: an empty frame says one goes there, and the
   record decides what fills it (a capability's evidence image, a category's
   first product photograph). */
.app-thumb--empty{display:block;height:84px;border:1px solid var(--line-2);
  border-radius:8px;background:var(--off)}
/* Every route out of this page — the capability and the category alike — stands here as
   a title rather than as a link: it reads in the site's ink, like a linked card heading
   (style.css), and warms to the capability brick red under the pointer. The website
   owner's instruction on the case page: black at rest, warm on hover. The arrow is not
   coloured here, so it follows the words. */
.app-links .cap-link,.app-links .tlink{color:var(--charcoal)}
.app-links .cap-link:hover,.app-links .cap-link:focus-visible,
.app-links .tlink:hover,.app-links .tlink:focus-visible{color:var(--warm)}
.app-links .cap-link::after{content:" \2192";display:inline-block;
  transition:transform var(--dur-fast) ease}
.app-links .cap-link:hover::after,.app-links .cap-link:focus-visible::after{transform:translateX(3px)}
.app-links{display:grid;gap:clamp(18px,2vw,26px)}
/* Two columns, one rule between them: the rule is the second column's own left
   border, and the gutter on either side of it belongs to the columns rather than
   to a gap between them, so the rule keeps its distance from both lists. */
.app-links--2{grid-template-columns:repeat(2,minmax(0,1fr));gap:0}
.app-links--2 .app-box{padding-right:clamp(34px,4.6vw,66px)}
.app-links--2 .app-box + .app-box{padding-right:0;
  padding-left:clamp(34px,4.6vw,66px);border-left:1px solid var(--line)}
@media (prefers-reduced-motion:reduce){
  .app-links .cap-link::after{transition:none}
  .app-links .cap-link:hover::after,.app-links .cap-link:focus-visible::after{transform:none}
}
/* Stacked on a narrow screen the rule turns with the columns: it becomes the line
   between the lists instead of the line beside them. */
@media (max-width:760px){
  .app-links--2{grid-template-columns:1fr;gap:clamp(26px,5vw,34px)}
  .app-links--2 .app-box{padding-right:0}
  .app-links--2 .app-box + .app-box{padding-left:0;border-left:0;
    border-top:1px solid var(--line);padding-top:clamp(30px,7vw,40px)}
}

/* ---------- The page's own index -------------------------------------------
   Stops that read as controls rather than as a line of text. Each one is the
   white button first, with the accent arriving as a wash rather than as the whole
   surface: --accent-t (tokens.css) drifts across the white ground and back, so the
   stop stays a white stop and only its tint moves. Pointed at, clicked or focused it
   becomes the flat accent with white type — 7.4:1, the state the site's buttons have.

   The gradient slides back and forth instead of looping: a loop would snap the
   colour back at the end of every cycle, which is exactly the flicker a row of
   buttons should not have. Each stop starts its drift at a different point, so the
   row ripples rather than pulsing in step.

   The row is centred under the first screen; it scrolls sideways on a phone
   instead of wrapping, so a narrow screen starts at the first stop. */
.app-jump{border-top:1px solid var(--line);border-bottom:1px solid var(--line);background:var(--off)}
.app-jump__in{display:flex;align-items:center;justify-content:center;
  gap:clamp(12px,1.6vw,22px);padding:14px 0;overflow-x:auto}
.app-jump__t{flex:0 0 auto;margin:0;font-size:11px;font-weight:700;letter-spacing:.15em;
  text-transform:uppercase;color:var(--muted)}
.app-jump ul{display:flex;flex:0 1 auto;gap:10px;margin:0;padding:0;list-style:none}
.app-jump li{flex:0 0 auto;margin:0}
.app-jump a{display:block;padding:9px 15px;border:1px solid var(--line-2);border-radius:var(--r-btn);
  background-color:var(--white);
  background-image:linear-gradient(100deg,var(--white) 0%,var(--accent-t) 50%,var(--white) 100%);
  background-size:200% 100%;background-position:0% 50%;
  color:var(--accent-d);font-size:13.5px;font-weight:600;white-space:nowrap;
  box-shadow:0 10px 18px -16px rgba(31,35,40,.5);
  animation:app-jump-drift 9s var(--ease) infinite alternate;
  transition:background-color var(--dur-fast) ease,border-color var(--dur-fast) ease,
  color var(--dur-fast) ease,box-shadow var(--dur-fast) ease}
/* One offset per stop: the wave, not a single pulse. */
.app-jump li:nth-child(1) a{animation-delay:-.6s}
.app-jump li:nth-child(2) a{animation-delay:-2.4s}
.app-jump li:nth-child(3) a{animation-delay:-4.2s}
.app-jump li:nth-child(4) a{animation-delay:-6s}
.app-jump li:nth-child(5) a{animation-delay:-7.8s}
/* Pointed at, focused or touched: the wash goes and the stop is one flat accent blue
   with white type. The drift keeps its position while paused, so the colour does not
   jump back when the pointer leaves. */
.app-jump a:hover,.app-jump a:focus-visible{background-image:none;background-color:var(--accent);
  border-color:var(--accent);color:#fff;text-decoration:none;animation-play-state:paused}
.app-jump a:active{background-color:var(--accent-d);border-color:var(--accent-d);color:#fff}
@keyframes app-jump-drift{
  from{background-position:0% 50%}
  to{background-position:100% 50%}
}
/* A reader who has asked for less motion keeps the same stop standing still. Only
   the drift is given up: pointed at, it still becomes the accent. */
@media (prefers-reduced-motion:reduce){
  .app-jump a{animation:none;background-image:none}
  .app-jump a:hover,.app-jump a:focus-visible{background-color:var(--accent);
    border-color:var(--accent);color:#fff}
  .app-jump a:active{background-color:var(--accent-d);border-color:var(--accent-d)}
}
@media (max-width:760px){
  .app-jump__in{justify-content:flex-start}
}

/* ---------- The closing enquiry band ---------------------------------------
   The form keeps the white card the site's forms use; the column only stops
   stretching, so the card is the width of the fields it holds. */
.app-quote__form{min-width:0}
