/* === The projects pages — the archive and the plates =======================
   Loaded by I_projects.html, ar/I_projects.html and the thirty
   <slug>-projects-detail.html pages, always AFTER css/ti-service.css and
   never on its own. Those pages carry class="main ts tp": everything that
   makes a page in this family — the tokens, the void, the hero, the acts, the
   rail, the chain, the buttons, the reveals, reduced motion and RTL — comes
   from the services stylesheet, which already serves eighteen live pages. If
   it is in here, it is something only the projects pages have.

   That is the whole answer to "I don't want the projects pages' motion to be
   a different, odd thing": they are not a second system, they are the same
   system with the photographs as its object.

   Prefix: tp-. ts- is the shared family, wf- the factory, ti- the homepage. */

/* -- 0. tokens ------------------------------------------------------------
   One per sector, and one thing only: the colour of the light in the room.
   The accent stays brand green everywhere — six accent colours would be six
   brands — but a restaurant and a warehouse should not be lit by the same
   bulb, and the wash is already a variable the stage light reads. */
.tp{
	--tp-card:#0e1116;
	--tp-card-2:#121620;
	--tp-i:rgba(255,255,255,.3);
}
.tp.tp-s-Restaurant{--ts-wash:rgba(230,190,140,.085)}
.tp.tp-s-Hotel{--ts-wash:rgba(214,198,160,.08)}
.tp.tp-s-Workplaces{--ts-wash:rgba(185,205,225,.075)}
.tp.tp-s-Sport{--ts-wash:rgba(199,214,160,.08)}
.tp.tp-s-Industrial{--ts-wash:rgba(170,190,205,.07)}
.tp.tp-s-RealEstate{--ts-wash:rgba(222,205,175,.08)}

/* -- 1. the hero ---------------------------------------------------------
   The detail page's hero is the services hero with one addition: the project
   is a proper noun and gets set like one, so the title holds the top of the
   page on its own without a kicker explaining it.

   The services theatre holds one object, in one place, and the hero copy
   never meets it. This theatre holds thirty lit photographs spread across the
   whole frame, so the hero and the spec card get the same scrim the acts
   already have — it is the only way white type over a moving, lit plate can
   be relied on to clear 4.5:1 at every scroll position. Drawn only where the
   theatre is live: with no WebGL there is nothing to scrim. */
.ts-gl .tp .ts-hero::before,.ts-gl .tp .ts-brief::before{content:"";
	position:absolute;inset:0;pointer-events:none;z-index:0;
	background:linear-gradient(to right,
		rgba(7,9,12,.95) 0%, rgba(7,9,12,.84) 26%, rgba(7,9,12,.44) 46%, transparent 64%)}
[dir="rtl"] .ts-gl .tp .ts-hero::before,[dir="rtl"] .ts-gl .tp .ts-brief::before{
	background:linear-gradient(to left,
		rgba(7,9,12,.95) 0%, rgba(7,9,12,.84) 26%, rgba(7,9,12,.44) 46%, transparent 64%)}
.tp .ts-hero > .ts-wrap,.tp .ts-brief > .ts-wrap{position:relative;z-index:1}
/* The card is a card: on these pages there is a photograph behind every part
   of it, not an empty room. */
.tp .ts-spec{background:rgba(7,9,12,.88)}
@supports not (backdrop-filter:blur(4px)){.tp .ts-spec{background:rgba(7,9,12,.96)}}
.tp h1.ts-h1{text-transform:uppercase;letter-spacing:-.025em}
[dir="rtl"] .tp h1.ts-h1{text-transform:none}
.tp .tp-hero-sector{display:inline-flex;align-items:center;gap:10px;
	font-family:"Overpass",sans-serif;font-weight:700;font-size:12px;
	letter-spacing:.16em;text-transform:uppercase;color:var(--ts-green);
	margin:0 0 clamp(14px,1.2vw,22px)}
[dir="rtl"] .tp .tp-hero-sector{letter-spacing:0;text-transform:none;font-size:14px}
.tp .tp-hero-sector::before{content:"";width:28px;height:1px;
	background:var(--ts-green)}

/* A services page has four acts; a project page has one per photograph, which
   is five on the longer ones. At the services pages' 130svh that is eight and
   a half screens before the copy below the theatre, and it is the same five
   photographs the contact sheet shows in one. Shorter acts keep the
   transformation readable as a transformation and the page readable as a
   page. */
.tp .ts-act{min-height:118svh}

/* The understudy: with no WebGL, js/ti-project.js puts each act's photograph
   into the act, on the side the camera would have put it. Never present when
   the theatre is live, so nothing is downloaded twice. */
.tp .tp-act-shot{margin:clamp(22px,2vw,36px) 0 0;width:100%}
.tp .tp-act-shot img{display:block;width:100%;height:auto}
/* The figure is appended inside .ts-act-inner, which is the flex row holding
   the copy column: below 992 that row becomes a column so the photograph
   stacks under its paragraph, and above it the figure leaves the flow and
   takes the side of the frame the camera would have put it on. */
.tp.tp-understudy .ts-act-inner{flex-direction:column}
@media (min-width:992px){
	.tp.tp-understudy .ts-act-inner{flex-direction:row}
	.tp .tp-act-shot{position:absolute;top:50%;transform:translateY(-50%);
		width:min(46vw,640px);margin:0;z-index:0}
	.tp .ts-act-a .tp-act-shot{inset-inline-end:0}
	.tp .ts-act-b .tp-act-shot{inset-inline-start:0}
	.tp.tp-understudy .ts-act-copy{position:relative;z-index:1}
}

/* The act copy on a project page is the client's own description, carried
   across the photographs rather than parked in one block below them. */
.tp .tp-act-fact{margin:0}
.tp .tp-act-fact .ts-fact-k{margin-bottom:8px}
.tp .tp-act-fact .ts-fact-v{font-size:clamp(19px,.6vw+15px,28px);line-height:1.3}

/* -- 2. the archive ------------------------------------------------------
   Thirty projects, on the dark surface. The board argued this one: a colour
   photograph on #0c0e12 is a lit object, and on stone it is a sticker. The
   two bands on these pages that are text — the proof band and a project's
   own description — keep the stone, so the family's theatre/office rhythm
   still reads. */
.tp .tp-archive{background:var(--ts-dark);scroll-margin-top:90px}
.tp .tp-archive-head{display:flex;align-items:flex-end;justify-content:space-between;
	gap:clamp(20px,3vw,60px);flex-wrap:wrap;margin-bottom:clamp(26px,2.4vw,44px)}
.tp .tp-count{font-family:"Overpass",sans-serif;font-weight:700;font-size:12px;
	letter-spacing:.14em;text-transform:uppercase;color:#7d838b;margin:0;
	white-space:nowrap}
[dir="rtl"] .tp .tp-count{letter-spacing:0;text-transform:none;font-size:14px}
.tp .tp-count-n{color:#fff;font-weight:800}

/* The pills. Real buttons in a real toolbar: tabbable, with their state
   announced, and the hash kept in step so the homepage's sector band can go
   on linking straight into a filtered view. */
.tp .tp-filter{display:flex;flex-wrap:wrap;gap:1px;margin:0 0 clamp(28px,2.6vw,52px);
	padding:0;background:var(--ts-line-2)}
.tp .tp-pill{appearance:none;border:0;background:var(--ts-dark-2);color:#9aa0a7;
	font-family:"Overpass",sans-serif;font-weight:700;font-size:12px;
	letter-spacing:.13em;text-transform:uppercase;cursor:pointer;
	padding:14px clamp(16px,1.4vw,28px);display:inline-flex;align-items:baseline;
	gap:9px;transition:background .25s ease,color .25s ease}
[dir="rtl"] .tp .tp-pill{letter-spacing:0;text-transform:none;font-size:14px}
.tp .tp-pill:hover{background:#171b23;color:#fff}
.tp .tp-pill:focus-visible{outline:2px solid var(--ts-green);outline-offset:-2px}
.tp .tp-pill.is-on{background:var(--ts-green);color:#111}
.tp .tp-pill-n{font-size:10px;font-weight:800;opacity:.72;letter-spacing:.04em}
.tp .tp-pill.is-on .tp-pill-n{opacity:.8}

/* -- 3. the cards --------------------------------------------------------
   Four across an iMac, because thirty cards at three across is nine screens
   of scrolling before the sixth row. Each carries what a client actually
   asks: what kind of space, which city, what year. */
.tp .tp-grid{display:grid;gap:clamp(18px,1.6vw,34px) clamp(16px,1.4vw,30px);
	grid-template-columns:minmax(0,1fr);align-items:start}
.tp .tp-card[hidden]{display:none}
.tp a.tp-card{display:block;text-decoration:none;color:inherit;position:relative}
.tp .tp-card-media{display:block;position:relative;overflow:hidden;
	background:var(--tp-card);aspect-ratio:4/3}
.tp .tp-card-media img{display:block;width:100%;height:100%;object-fit:cover;
	/* The archive is calm and the hover is the reward. Not full grayscale:
	   these are daylight interiors and taking all the colour out of thirty of
	   them makes a wall of grey. */
	filter:grayscale(.55) contrast(1.04);
	transform:scale(1.001);
	transition:filter .5s ease,transform .7s cubic-bezier(.22,1,.36,1)}
.tp a.tp-card:hover .tp-card-media img,
.tp a.tp-card:focus-visible .tp-card-media img{filter:none;transform:scale(1.055)}
.tp .tp-card-media::after{content:"";position:absolute;inset:0;pointer-events:none;
	box-shadow:inset 0 0 0 1px rgba(255,255,255,.08);
	background:linear-gradient(to top,rgba(7,9,12,.55),transparent 42%);
	transition:box-shadow .3s ease}
.tp a.tp-card:hover .tp-card-media::after{box-shadow:inset 0 0 0 1px rgba(187,214,54,.5)}
.tp a.tp-card:focus-visible{outline:2px solid var(--ts-green);outline-offset:4px}

.tp .tp-card-i{position:absolute;inset-inline-start:14px;top:12px;z-index:2;
	font-family:"Overpass",sans-serif;font-weight:800;font-size:clamp(22px,1.5vw,30px);
	line-height:1;color:transparent;-webkit-text-stroke:1px var(--tp-i);
	transition:-webkit-text-stroke-color .35s ease}
.tp a.tp-card:hover .tp-card-i{-webkit-text-stroke-color:rgba(187,214,54,.85)}

.tp .tp-card-body{display:block;padding:14px 2px 0}
.tp .tp-card-h{display:block;font-family:"Poppins",sans-serif;font-weight:600;
	font-size:clamp(16px,.42vw+13px,21px);line-height:1.25;color:#fff;
	letter-spacing:-.005em;
	background-image:linear-gradient(var(--ts-green),var(--ts-green));
	background-repeat:no-repeat;background-position:0 100%;background-size:0 1px;
	transition:background-size .4s cubic-bezier(.22,1,.36,1);
	width:fit-content;max-width:100%}
[dir="rtl"] .tp .tp-card-h{background-position:100% 100%}
.tp a.tp-card:hover .tp-card-h,.tp a.tp-card:focus-visible .tp-card-h{background-size:100% 1px}
.tp .tp-card-m{display:block;margin-top:7px;font-family:"Overpass",sans-serif;
	font-weight:700;font-size:11px;letter-spacing:.12em;text-transform:uppercase;
	color:#7d838b}
[dir="rtl"] .tp .tp-card-m{letter-spacing:0;text-transform:none;font-size:13px}

/* Dealt in, not faded in: the index sets the delay, so a filtered set arrives
   as a set. Script adds .tp-anim after first paint — without it every card is
   simply there, which is what a visitor with no JavaScript gets. */
.tp-anim .tp-card{opacity:0;transform:translateY(26px)}
.tp-anim .tp-card.is-dealt{animation:tp-deal .72s cubic-bezier(.22,1,.36,1) forwards;
	animation-delay:calc(var(--i,0) * 55ms)}
@keyframes tp-deal{to{opacity:1;transform:none}}

/* -- 4. the proof band ---------------------------------------------------- */
.tp .tp-proof-n small{display:block;font-size:.3em;letter-spacing:.14em;
	text-transform:uppercase;margin:10px 0 0;color:var(--ts-ink-2);font-weight:700}
[dir="rtl"] .tp .tp-proof-n small{letter-spacing:0;text-transform:none}

/* -- 5. a project's own page: the record ---------------------------------
   The stone band, and the only part of this page that is not photographs.
   Facts, not prose: the description is already on the page, beside the
   photographs it describes, and a visitor scrolling through should not meet
   the same paragraph twice. The company's own attribution line is kept here
   rather than dropped at the end of the client's text as though the client
   had written it. */
.tp .tp-record-grid{display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);
	gap:clamp(32px,4vw,100px);align-items:start}
.tp .tp-note{margin:clamp(26px,2.2vw,40px) 0 0;padding-top:18px;
	border-top:1px solid var(--ts-line-ink);font-size:13px;letter-spacing:.04em;
	color:var(--ts-ink-3);max-width:46ch}

.tp dl.tp-facts{margin:0;padding:0}
.tp .tp-facts div{display:flex;justify-content:space-between;align-items:baseline;
	gap:18px;padding:clamp(15px,1.3vw,22px) 0;border-top:1px solid var(--ts-line-ink)}
.tp .tp-facts div:first-child{border-top:0;padding-top:0}
.tp .tp-facts dt{font-family:"Overpass",sans-serif;font-weight:700;font-size:11px;
	letter-spacing:.14em;text-transform:uppercase;color:var(--ts-ink-3);margin:0;
	flex:none}
[dir="rtl"] .tp .tp-facts dt{letter-spacing:0;text-transform:none;font-size:13px}
.tp .tp-facts dd{margin:0;font-family:"Overpass",sans-serif;font-weight:700;
	font-size:clamp(15px,.3vw+13px,19px);color:var(--ts-ink);text-align:end;
	line-height:1.4}
.tp .tp-record-cta{margin-top:clamp(26px,2.4vw,44px);display:flex;flex-wrap:wrap;gap:12px}
/* ts-btn-ghost is a white-on-dark button. This is the same shape for the
   stone surface, where white on stone is invisible. */
.tp .ts-btn-ink{background:transparent;color:var(--ts-ink);
	box-shadow:inset 0 0 0 1px rgba(42,42,42,.28)}
.tp .ts-btn-ink:hover,.tp .ts-btn-ink:focus-visible{background:rgba(42,42,42,.07);
	color:var(--ts-ink)}
.tp .ts-btn-ink:focus-visible{outline:2px solid var(--ts-green-ink);outline-offset:3px}

/* -- 6. the contact sheet ------------------------------------------------
   Everything you just flew through, in order, at a size you can scan — and
   the only copy of the photographs that is in the DOM. The theatre is an
   enhancement; this is the page. */
.tp .tp-sheet-grid{display:grid;gap:clamp(10px,1vw,20px);
	grid-template-columns:repeat(2,minmax(0,1fr))}
.tp a.tp-sheet-item{display:block;position:relative;overflow:hidden;
	background:var(--tp-card);aspect-ratio:4/3}
.tp .tp-sheet-item img{display:block;width:100%;height:100%;object-fit:cover;
	transition:transform .8s cubic-bezier(.22,1,.36,1),filter .4s ease}
.tp a.tp-sheet-item:hover img,.tp a.tp-sheet-item:focus-visible img{transform:scale(1.05)}
.tp .tp-sheet-item::after{content:"";position:absolute;inset:0;pointer-events:none;
	box-shadow:inset 0 0 0 1px rgba(255,255,255,.07);transition:box-shadow .3s ease}
.tp a.tp-sheet-item:hover::after{box-shadow:inset 0 0 0 1px rgba(187,214,54,.45)}
.tp a.tp-sheet-item:focus-visible{outline:2px solid var(--ts-green);outline-offset:3px}
.tp .tp-sheet-n{position:absolute;inset-inline-end:12px;bottom:10px;z-index:2;
	font-family:"Overpass",sans-serif;font-weight:800;font-size:12px;
	letter-spacing:.1em;color:rgba(255,255,255,.72);
	text-shadow:0 1px 10px rgba(0,0,0,.6)}
.tp-anim .tp-sheet-item{opacity:0;transform:translateY(22px)}
.tp-anim .tp-sheet-item.is-dealt{animation:tp-deal .7s cubic-bezier(.22,1,.36,1) forwards;
	animation-delay:calc(var(--i,0) * 60ms)}

/* -- 7. the sector rail --------------------------------------------------
   ts-reel, with a photograph instead of a drawn plan. Same scroller, same
   drive, same buttons — js/ti-service.js runs it without knowing. */
.tp .ts-reel-cell.tp-reel-cell{padding:0;overflow:hidden}
.tp .tp-reel-img{display:block;width:100%;aspect-ratio:4/3;object-fit:cover;
	filter:grayscale(.5);transition:filter .5s ease,transform .7s cubic-bezier(.22,1,.36,1)}
.tp .ts-reel-cell:hover .tp-reel-img,
.tp .ts-reel-cell.is-near .tp-reel-img{filter:none;transform:scale(1.04)}
.tp .tp-reel-body{padding:clamp(18px,1.6vw,30px)}
.tp .tp-reel-body h3{margin-bottom:6px}
.tp .tp-reel-body p{margin-bottom:clamp(16px,1.4vw,26px)}

/* -- 8. the chain --------------------------------------------------------
   The next project, named, with its cover already rising into the room
   behind it. Thirty pages, one scroll. */
.tp .tp-chain-meta{display:block;margin-top:clamp(14px,1.2vw,22px);
	font-family:"Overpass",sans-serif;font-weight:700;font-size:12px;
	letter-spacing:.14em;text-transform:uppercase;color:#7d838b}
[dir="rtl"] .tp .tp-chain-meta{letter-spacing:0;text-transform:none;font-size:14px}

/* -- 9. responsive -------------------------------------------------------
   Explicit column counts rather than auto-fit: the drift that staggers the
   cards is computed per visible card by script, and a column count the
   stylesheet cannot name is a drift nobody can predict. */
@media (min-width:768px){
	.tp .tp-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
	.tp .tp-sheet-grid{grid-template-columns:repeat(4,minmax(0,1fr))}
	/* The first photograph is the one the project is known by: twice the size
	   of the rest, and first in the reading order. */
	.tp .tp-sheet-item:first-child{grid-column:span 2;grid-row:span 2;aspect-ratio:auto}
}
@media (min-width:1200px){
	.tp .tp-grid{grid-template-columns:repeat(3,minmax(0,1fr))}
	/* The stagger. Script sets --drift (0, 1 or 2) on every visible card, so
	   filtering re-deals the diagonal instead of leaving holes in it. */
	.tp .tp-card{margin-top:calc(var(--drift,0) * clamp(14px,2.2vw,46px))}
}
@media (min-width:1500px){
	.tp .tp-grid{grid-template-columns:repeat(4,minmax(0,1fr))}
}
@media (max-width:991px){
	.tp .tp-record-grid{grid-template-columns:minmax(0,1fr);gap:clamp(28px,4vw,48px)}
	.tp .tp-archive-head{align-items:flex-start}
	/* Below 992 the object is centred rather than pushed aside, so the scrim
	   stops being a side wash and becomes a floor — the same decision the
	   services acts make at the same breakpoint. */
	.ts-gl .tp .ts-hero::before,.ts-gl .tp .ts-brief::before,
	[dir="rtl"] .ts-gl .tp .ts-hero::before,[dir="rtl"] .ts-gl .tp .ts-brief::before{
		background:linear-gradient(to bottom,
			rgba(7,9,12,.5) 0%, rgba(7,9,12,.84) 40%, rgba(7,9,12,.92) 100%)}
}
@media (max-width:600px){
	.tp .tp-pill{padding:12px 14px;font-size:11px;letter-spacing:.1em}
	.tp .tp-sheet-grid{gap:8px}
}

/* -- 10. the theme, turned off where it fights us -------------------------
   The theme gives #footer a 50px top margin. On a white page that is white
   and nobody notices; on a page with a fixed canvas behind it, it is a
   50-pixel window onto a photograph, between the call to action and the
   footer. The sections above already carry their own padding. */
.main.tp + #footer{margin-top:0}

/* -- 11. reduced motion --------------------------------------------------- */
@media (prefers-reduced-motion:reduce){
	.tp-anim .tp-card,.tp-anim .tp-sheet-item{opacity:1;transform:none;animation:none}
	.tp .tp-card-media img,.tp .tp-sheet-item img,.tp .tp-reel-img{transition:none}
	.tp a.tp-card:hover .tp-card-media img,
	.tp a.tp-sheet-item:hover img{transform:none}
}
