From a0ec74b09bc770c0aa669048d53864967ba9ea9c Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 5 Nov 2026 17:30:24 +0900 Subject: [PATCH] Improve drawing symbols and sample popup Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- 도면작성/BEARING_SYMBOLS.md | 7 +++++ 도면작성/bearing-detail.ts | 35 ++++++++++++++++------- 도면작성/dimension-graphics.ts | 4 +-- 도면작성/drawing.test.ts | 11 ++++++++ 도면작성/dxf.ts | 2 +- 도면작성/quality-layout.ts | 2 +- 도면작성/types.ts | 2 +- 랜딩페이지/landing-guide.ts | 4 +-- 랜딩페이지/landing-page.ts | 12 +++++++- 랜딩페이지/sample-explorer.ts | 6 ++-- 랜딩페이지/sample-library-popup.css | 44 +++++++++++++++++++++++++++++ 11 files changed, 108 insertions(+), 21 deletions(-) create mode 100644 도면작성/BEARING_SYMBOLS.md create mode 100644 랜딩페이지/sample-library-popup.css diff --git a/도면작성/BEARING_SYMBOLS.md b/도면작성/BEARING_SYMBOLS.md new file mode 100644 index 0000000..365efef --- /dev/null +++ b/도면작성/BEARING_SYMBOLS.md @@ -0,0 +1,7 @@ +# 받침 상세 문자치수와 치수표 + +상세 A의 A/B(받침 본체 평면), C(전·후열 중심 간격), D(일체 교좌면 길이), S(%), T/T1/T2(솔플레이트), h1/h2/h3/E(높이)를 문자 기호로 표시한다. + +표는 기존 bearingSchedule의 현재 모델 계산값을 사용한다. A/B/C/T1/T2/E 치수 객체는 실제 mm 수치를 value로 유지하고 label만 문자로 표시한다. SVG와 DXF DIMENSION 문자 재정의에 같은 label을 적용한다. D는 일체 교좌면이 있는 경우만 표기한다. 미확인 제원은 값을 임의로 만들지 않는다. 첨부 참고 이미지의 3100, 1200, 50 등을 현재 모델의 실제값으로 복사하지 않는다. + +기존 받침 번호와 EL.X, 높이·표고표를 유지한다. 수정 범위는 도면작성 폴더 내부다. diff --git a/도면작성/bearing-detail.ts b/도면작성/bearing-detail.ts index ae3898d..9ddb000 100644 --- a/도면작성/bearing-detail.ts +++ b/도면작성/bearing-detail.ts @@ -98,7 +98,7 @@ export function bearingDetail(model:PierModel,scale:number,key?:string,dimension } const line=(a:Point,b:Point,layer:Entity['layer']='CENTER')=>entities.push({kind:'line',a,b,layer,source}); const text=(point:Point,t:string,height=1.8*scale,align:'left'|'center'|'right'='left')=>entities.push({kind:'text',point,text:t,height,align,layer:'TEXT',source}); - const dim=(a:Point,b:Point,position:number,axis:'x'|'y')=>entities.push({kind:'dimension',a,b,position,axis,value:Math.abs(axis==='x'?a[0]-b[0]:a[1]-b[1]),height:1.7*scale,layer:'DIMENSION',source}); + const dim=(a:Point,b:Point,position:number,axis:'x'|'y',label?:string)=>entities.push({kind:'dimension',a,b,position,axis,value:Math.abs(axis==='x'?a[0]-b[0]:a[1]-b[1]),height:1.7*scale,label,layer:'DIMENSION',source}); blocks.forEach((block,i)=>{ const x=(local[i].z-cx)*1000,z=(block.padTop-datum)*1000,idx=model.blocks.indexOf(block),s={member:'bearing-number',index:idx,pier:model.name}; entities.push({kind:'circle',center:[x,z+6*scale],radius:1.7*scale,layer:'TEXT',source:s}); @@ -114,19 +114,34 @@ export function bearingDetail(model:PierModel,scale:number,key?:string,dimension ['h1','h2','h3','T'].slice(0,zs.length-1).forEach((name,j)=>{ const yy=(zs[j]+zs[j+1])/2,target=(j*3-2)*scale; line([x+sign*DIM.padSize*500,yy],[near,target],'TEXT'); - text([near+sign*scale,target],`${name}=${value(zs[j+1]-zs[j])}`,1.55*scale,sign<0?'right':'left'); + text([near+sign*scale,target],name,1.55*scale,sign<0?'right':'left'); }); } }); - if(pair&&dimensions){const a=(local[0].z-cx)*1000,b=(local[1].z-cx)*1000;dim([a,planY],[b,planY],planY-1000,'x');text([0,planY-1000-3*scale],'C · 전/후열 중심 간격',1.6*scale,'center');} + if(pair&&dimensions){const a=(local[0].z-cx)*1000,b=(local[1].z-cx)*1000;dim([a,planY],[b,planY],planY-1000,'x','C');} const ib=model.integratedBlocks.find(b=>blocks.some(x=>x.row==='전열'&&x.no===b.frontNo)); - if(ib)text([0,planY-1000-6*scale],`D=${value(INTEGRATED_BLOCK.length)} (일체 교좌면 길이)`,1.6*scale,'center'); - const plan=selected.plan; - if(plan){ - const p=solplateGeometry(selected,plan.h3,{A:plan.A,B:plan.B,margin:plan.margin,T:plan.T,slopePct:plan.S}); - text([0,-650],`솔플 L=${value(p.L*1000)} / W=${value(p.W*1000)} · S=${plan.S.toFixed(3)}%`,1.6*scale,'center'); - text([0,-800],`T=${value(plan.T)} / T1=${value(p.T1*1000)} / T2=${value(p.T2*1000)}`,1.6*scale,'center'); - }else text([0,-650],'받침 본체·솔플레이트 제원 미확인',1.65*scale,'center'); + if(ib&&dimensions)dim([-INTEGRATED_BLOCK.length/2,planY-600],[INTEGRATED_BLOCK.length/2,planY-600],planY-1000-5*scale,'x','D'); + const rows=bearingSchedule(model); + if(dimensions)blocks.forEach((block,i)=>{ + const row=rows.find(r=>r.index===model.blocks.indexOf(block))!,p=block.plan; + const x=(local[i].z-cx)*1000,y=(local[i].x-cy)*1000+planY; + if(!p)return; + // Symbolic dimensions retain measured numeric values for CAD and the schedule. + dim([x-p.A/2,y],[x+p.A/2,y],planY+700+3*scale,'x','A'); + const side=i===0?-1:1; + dim([x,y-p.B/2],[x,y+p.B/2],x+side*(p.A/2+4*scale),'y','B'); + const z=(block.padTop+p.h3/1000-datum)*1000; + const half=(row.L??p.A)/2; + if(row.E!==null)dim([x,(block.bottom-datum)*1000],[x,(block.bottom-datum)*1000+row.E],x+(i===0?-1:1)*(half+7*scale),'y','E'); + for(const [symbol,edge,thickness] of [['T1',-1,row.T1],['T2',1,row.T2]] as const){ + if(thickness!==null)dim([x+edge*half,z],[x+edge*half,z+thickness],x+edge*(half+2*scale),'y',symbol); + } + const slopeY=z+(row.T??0)+4*scale; + line([x-half,slopeY],[x+half,slopeY],'DIMENSION'); + line([x+half,slopeY],[x+half-1.5*scale,slopeY+scale],'DIMENSION'); + text([x,slopeY+scale],'S(%)',1.7*scale,'center'); + }); + text([0,-650],blocks.some(b=>!b.plan)?'받침 제원 미확인 · 치수표 참조':'문자 치수: 치수표 참조',1.4*scale,'center'); text([0,-1040],'교좌면 평면 · 가로 교축(n) / 세로 교직(s)',1.65*scale,'center'); return{entities,title:`상세 A · ${blocks.map(b=>`${b.no}(${b.row==='전열'?'전':'후'})`).join(' / ')}`}; } diff --git a/도면작성/dimension-graphics.ts b/도면작성/dimension-graphics.ts index a22a8b3..4e009bb 100644 --- a/도면작성/dimension-graphics.ts +++ b/도면작성/dimension-graphics.ts @@ -6,9 +6,9 @@ export function dimensionGraphics(entity:Extract):Ent const p:Point=axis==='x'?[a[0],position]:[position,a[1]],q:Point=axis==='x'?[b[0],position]:[position,b[1]],u:Point=axis==='x'?[Math.sign(q[0]-p[0])||1,0]:[0,Math.sign(q[1]-p[1])||1]; const n:Point=[-u[1],u[0]],line=(a:Point,b:Point):Entity=>({kind:'line',a,b,source,layer}); const ext=(a:Point,b:Point):Entity=>{const d=Math.hypot(b[0]-a[0],b[1]-a[1]),k=d?1/d:0,dx=(b[0]-a[0])*k,dy=(b[1]-a[1])*k;return line([a[0]+dx*Math.min(CAD_STYLE.extensionGap*s,d),a[1]+dy*Math.min(CAD_STYLE.extensionGap*s,d)],[b[0]+dx*s,b[1]+dy*s]);}; - const length=Math.hypot(q[0]-p[0],q[1]-p[1]),textWidth=formatMm(entity.value).length*height*.6,external=length[point[0]+arrow*.5*Math.cos(i*Math.PI/8),point[1]+arrow*.5*Math.sin(i*Math.PI/8)] as Point),pattern:'SOLID',spacing:s,angle:0,color:'#ff0000',source,layer}); - out.push({kind:'text',point:[(p[0]+q[0])/2+(axis==='y'?-1.2*s:0),(p[1]+q[1])/2+(axis==='x'?1.2*s:0)],text:formatMm(entity.value),height,rotation:axis==='y'?90:0,align:'center',source,layer}); + out.push({kind:'text',point:[(p[0]+q[0])/2+(axis==='y'?-1.2*s:0),(p[1]+q[1])/2+(axis==='x'?1.2*s:0)],text:entity.label??formatMm(entity.value),height,rotation:axis==='y'?90:0,align:'center',source,layer}); return out; } diff --git a/도면작성/drawing.test.ts b/도면작성/drawing.test.ts index 0dd6265..c94fba2 100644 --- a/도면작성/drawing.test.ts +++ b/도면작성/drawing.test.ts @@ -129,6 +129,17 @@ describe('실제 모델 → 일반도',()=>{ expect(drawing.views.find(v=>v.id==='detail')!.entities.some(e=>e.source.member==='solplate')).toBe(true); const cd=drawing.views.find(v=>v.id==='detail')!.entities.find(e=>e.kind==='dimension'); expect(cd?.kind==='dimension'?cd.value:null).toBeCloseTo(r.C!,6); + const detail=drawing.views.find(v=>v.id==='detail')!; + for(const key of ['A','B','C','T1','T2'] as const){ + const dimension=detail.entities.find(e=>e.kind==='dimension'&&e.label===key); + expect(dimension?.kind==='dimension'?dimension.value:null).toBeCloseTo(r[key]!,6); + } + expect(detail.entities.some(e=>e.kind==='text'&&e.text==='S(%)')).toBe(true); + expect(detail.entities.some(e=>e.kind==='text'&&e.text==='h1')).toBe(true); + const symbolicDxf=exportDxf(drawing,layers); + expect(symbolicDxf).toContain('\r\n1\r\nT1\r\n'); + expect(drawingSvg(drawing,layers,true)).toContain('T1'); + const uneven={...input,bearings:input.bearings.filter(b=>!(b.row==='전열'&&b.no===5))},um=buildPier(uneven); expect(bearingSchedule(um)).toHaveLength(uneven.bearings.length); const ud=generateDrawing(um,uneven,project,{...DEFAULT_SETTINGS,detailBearing:'후열:10'}); diff --git a/도면작성/dxf.ts b/도면작성/dxf.ts index a164444..0f998ad 100644 --- a/도면작성/dxf.ts +++ b/도면작성/dxf.ts @@ -66,7 +66,7 @@ export function exportDxf(doc: DrawingDocument,visible: ReadonlySet): str const a=translate(e.a,offset),b2=translate(e.b,offset); const definition:Point=e.axis==='x'?[b2[0],e.position+offset[1]]:[e.position+offset[0],b2[1]]; const mid:Point=e.axis==='x'?[(a[0]+b2[0])/2,definition[1]+e.height]:[definition[0]-e.height,(a[1]+b2[1])/2]; - entities+=pairs(0,'DIMENSION')+common(e,modelRecord,view.scale)+pairs(100,'AcDbDimension',2,name,10,definition[0],20,definition[1],30,0,11,mid[0],21,mid[1],31,0,70,32,1,'<>',3,`PZ_${view.scale}`,42,e.value, + entities+=pairs(0,'DIMENSION')+common(e,modelRecord,view.scale)+pairs(100,'AcDbDimension',2,name,10,definition[0],20,definition[1],30,0,11,mid[0],21,mid[1],31,0,70,32,1,e.label??'<>',3,`PZ_${view.scale}`,42,e.value, 100,'AcDbAlignedDimension',13,a[0],23,a[1],33,0,14,b2[0],24,b2[1],34,0,50,e.axis==='x'?0:90,100,'AcDbRotatedDimension')+xdata(e,view.id); } const source={member:'view-title',index:0,pier:doc.pier}; diff --git a/도면작성/quality-layout.ts b/도면작성/quality-layout.ts index 0ea9281..32f7db3 100644 --- a/도면작성/quality-layout.ts +++ b/도면작성/quality-layout.ts @@ -32,7 +32,7 @@ function dimensionLanes(view:DrawingView):void{ dims.sort((a,b)=>a.value-b.value); for(const e of dims){ const a=e.axis==='x'?e.a[0]/s:shortenedY(e.a[1],view.breaks)/s,c=e.axis==='x'?e.b[0]/s:shortenedY(e.b[1],view.breaks)/s; - const lo=Math.min(a,c),hi=Math.max(a,c),width=textWidth(e.value.toLocaleString('en-US',{maximumFractionDigits:1}),CAD_STYLE.dimensionText),mid=(lo+hi)/2; + const lo=Math.min(a,c),hi=Math.max(a,c),width=textWidth(e.label??e.value.toLocaleString('en-US',{maximumFractionDigits:1}),CAD_STYLE.dimensionText),mid=(lo+hi)/2; const item={lo,hi,textLo:mid-width/2-.6,textHi:mid+width/2+.6}; let lane=0; while(lanes[lane]?.some(p=>Math.min(p.hi,item.hi)-Math.max(p.lo,item.lo)>.05||Math.min(p.textHi,item.textHi)-Math.max(p.textLo,item.textLo)>0))lane++; diff --git a/도면작성/types.ts b/도면작성/types.ts index 422533f..f02b1ce 100644 --- a/도면작성/types.ts +++ b/도면작성/types.ts @@ -12,7 +12,7 @@ export type Entity = Base & ( { kind: 'circle'; center: Point; radius: number } | { kind: 'hatch'; points:Point[]; holes?:Point[][];pattern:'SOLID'|'ANSI31'; spacing:number; angle:number; color?:string } | { kind: 'text'; point: Point; text: string; height: number; align?: 'left' | 'center' | 'right'; rotation?: number } | - { kind: 'dimension'; a: Point; b: Point; position: number; axis: 'x' | 'y'; height: number; value: number } + { kind: 'dimension'; a: Point; b: Point; position: number; axis: 'x' | 'y'; height: number; value: number; label?: string } ); export type Bounds = { minX: number; minY: number; maxX: number; maxY: number }; export type Rect = { x: number; y: number; width: number; height: number }; diff --git a/랜딩페이지/landing-guide.ts b/랜딩페이지/landing-guide.ts index 457bd5e..05a27a9 100644 --- a/랜딩페이지/landing-guide.ts +++ b/랜딩페이지/landing-guide.ts @@ -54,7 +54,7 @@ export function createLandingGuide(root: HTMLElement): {close:(restoreFocus?:boo '종평면도·받침배치도·ELEVATION DWG가 들어 있는 폴더를 선택하고 자동 추출 시작을 누르세요. 진행 내용이 팝업에 표시됩니다.', '결과 목록의 교각 불러오기를 눌러 Excel 인식 결과를 확인하고 형상을 생성합니다. Excel 저장으로 파일만 받을 수도 있습니다.'], sample:['파일 준비 없이 실제 교각과 지반 모델을 살펴보려면 샘플 프로젝트를 선택하세요.', - '오른쪽 고정 패널에서 산외교·운곡교 등 12개 교량의 이름과 교각 수를 확인하고 원하는 교량을 선택합니다.', + '설계 라이브러리 팝업에서 산외교·운곡교 등 12개 교량의 이름과 교각 수를 확인하고 원하는 교량을 선택합니다.', `현재 ${sampleProjects.filter(s=>s.pierCount>0).length}개 교량은 교각과 원도면 지반면, 위성사진, 2D 지도를 함께 엽니다. 교각 데이터가 없는 승천천교는 지반·지도 자료를 표시합니다. 설계 지반고는 Excel 원래 값을 유지하며, 기존 검증 알림은 확인이 필요합니다.`] }; const facts: Record={ @@ -117,7 +117,7 @@ export function createLandingGuide(root: HTMLElement): {close:(restoreFocus?:boo dialog.querySelector('.landing-guide-prev')!.addEventListener('click',()=>{step=Math.max(0,step-1);render();if(step===0)dialog.querySelector('[data-guide-step="0"]')!.focus();}); dialog.querySelector('.landing-guide-next')!.addEventListener('click',()=>{ if(step<2){step++;render();return;} - close(false);if(mode==='sample'){root.querySelector('.sample-load')?.focus();return;}root.querySelector(`[data-card="${mode}"] button`)?.click(); + close(false);if(mode==='sample'){root.querySelector('[data-card=sample] button')?.click();return;}root.querySelector(`[data-card="${mode}"] button`)?.click(); }); // Main-app functions may hide this overlay without calling landing functions. new MutationObserver(()=>{if(root.hidden)close(false);}).observe(root,{attributes:true,attributeFilter:['hidden']}); diff --git a/랜딩페이지/landing-page.ts b/랜딩페이지/landing-page.ts index 69228dd..de53fa2 100644 --- a/랜딩페이지/landing-page.ts +++ b/랜딩페이지/landing-page.ts @@ -5,6 +5,7 @@ import { createLandingGuide } from './landing-guide'; import { createHeroMotion, heroMotionMarkup } from './landing-motion'; import './landing-white.css'; import './landing-typography.css'; +import './sample-library-popup.css'; import { createDrawingPopup, drawingPopupMarkup } from './drawing-popup'; const appRoot = document.querySelector('.app'); @@ -81,6 +82,10 @@ const landingPanelMarkup = ` +
+
03 / EXPLORE
Design Library

기설계교량 라이브러리

기존 설계 프로젝트의 교각·지형·지도를 살펴보고 불러옵니다.

+
12개 설계 프로젝트
+
@@ -89,7 +94,7 @@ const landingPanelMarkup = `
- ${sampleExplorerMarkup} + ${sampleExplorerMarkup}

엑셀 입력

@@ -135,6 +140,7 @@ const closePanels = (): void => { }; const showApp = (): void => { + landingOverlay.querySelector('.sample-library-popup')?.close(); landingGuide.close(false); if (appRoot) { appRoot.hidden = false; @@ -262,6 +268,10 @@ document.querySelectorAll('[data-pick-file]').forEach((button bindFilePicker(document.querySelector('[data-pick-file="excel"]') as HTMLButtonElement, 'landing-excel-input', 'landing-excel-name'); +const libraryPopup=landingOverlay.querySelector('.sample-library-popup')!; +landingOverlay.querySelector('[data-card="sample"] button')!.addEventListener('click',()=>{landingGuide.close(false);landingGuide.dismissHint();if(!libraryPopup.open)libraryPopup.showModal();}); +libraryPopup.querySelector('.sample-library-close')!.addEventListener('click',()=>libraryPopup.close()); +libraryPopup.addEventListener('close',()=>{requestAnimationFrame(()=>{landingOverlay.querySelectorAll('.landing-shell,.landing-hero').forEach(element=>element.scrollTo(0,0));landingOverlay.querySelector('[data-card=sample] button')?.focus({preventScroll:true});});}); createSampleExplorer(landingOverlay,(sample:Sample)=>loadSampleIntoApp(sample,showApp,showLanding)); if (homeButton) { diff --git a/랜딩페이지/sample-explorer.ts b/랜딩페이지/sample-explorer.ts index adc4e47..6440fb5 100644 --- a/랜딩페이지/sample-explorer.ts +++ b/랜딩페이지/sample-explorer.ts @@ -6,10 +6,10 @@ import './sample-explorer.css'; export type Sample=typeof sampleProjects[number]; export const sampleExplorerMarkup=``; @@ -38,7 +38,7 @@ export function createSampleExplorer(root:HTMLElement,load:(sample:Sample)=>Prom row.addEventListener('click',()=>select(sample));for(const event of ['pointerenter','focus'])row.addEventListener(event,()=>{row.classList.add('is-hovered');map?.hover(sample.sample);});for(const event of ['pointerleave','blur'])row.addEventListener(event,()=>{row.classList.remove('is-hovered');map?.hover(null);});list.append(row); } map=createSampleMap(panel,id=>{const sample=sampleProjects.find(s=>s.sample===id);if(sample){select(sample);list.querySelector(`[data-sample-row="${id}"]`)?.scrollIntoView({block:'nearest'});}},()=>selected.sample,()=>busy); - panel.querySelector('.sample-locate')!.addEventListener('click',()=>map!.focus(selected.sample,true)); + button.addEventListener('click',async()=>{ if(busy)return;const target=selected;busy=true;button.disabled=true;button.textContent=`${target.bridge} 불러오는 중…`;list.querySelectorAll('button').forEach(b=>b.disabled=true);map!.refresh();status.textContent=`${target.bridge}: 실제 프로젝트 파일을 여는 중…`; try{await load(target);status.textContent=`${target.bridge}: 교각 ${target.pierCount}기 · 실제 지반/지도 복원 완료`;} diff --git a/랜딩페이지/sample-library-popup.css b/랜딩페이지/sample-library-popup.css new file mode 100644 index 0000000..9aefd7b --- /dev/null +++ b/랜딩페이지/sample-library-popup.css @@ -0,0 +1,44 @@ +.landing-white .landing-shell{grid-template-columns:minmax(0,1fr)} +.landing-white .landing-cards{grid-template-columns:repeat(3,minmax(0,1fr));width:min(1280px,92%)} +.landing-white .landing-card h3{font-size:clamp(16px,1.25vw,21px)} +.sample-library-popup{box-sizing:border-box;width:min(1240px,90vw);height:90dvh;max-width:94vw;max-height:94dvh;padding:0;border:1px solid #d6e5f1;border-radius:20px;background:#f8fbff;color:#14263d;overflow:hidden;box-shadow:0 30px 100px #14263d40} +.sample-library-popup::backdrop{background:#14263d66;backdrop-filter:blur(6px)} +.sample-library-close{position:absolute;right:22px;top:18px;z-index:10;width:38px;height:38px;border:1px solid #d6e5f1;border-radius:10px;background:white;color:#325670;font-size:24px;cursor:pointer} +.sample-library-popup .sample-explorer{height:100%;min-height:0;padding:24px;gap:18px 24px;grid-template-columns:minmax(0,1fr) minmax(300px,.48fr);grid-template-rows:auto minmax(0,1fr) minmax(0,1fr);border:0;box-shadow:none;background:linear-gradient(140deg,#fff,#f2f8fe)} +.sample-library-popup .sample-explorer-head{grid-column:1/-1;padding-right:62px;display:grid;grid-template-columns:minmax(0,1fr) minmax(280px,360px) auto;gap:20px;align-items:center} +.sample-library-popup .sample-map-section{grid-column:1;grid-row:2} +.sample-library-popup .sample-selection{grid-column:1;grid-row:3;overflow:hidden;display:grid;grid-template-rows:auto minmax(0,1fr);gap:0} +.sample-library-popup .sample-list-section{grid-column:2;grid-row:2/4;min-height:0;border-top:0;border-left:1px solid #dce6f0;padding:0 0 0 24px;grid-template-rows:auto minmax(0,1fr) auto;gap:14px} +.sample-library-popup .sample-list{max-height:none;grid-auto-rows:64px;gap:8px;grid-template-columns:minmax(0,1fr)} +.sample-library-popup .sample-row{min-height:64px;padding:10px;gap:10px;grid-template-columns:28px minmax(0,1fr)} +.sample-library-popup .sample-row-number{width:28px;height:30px;font-size:11px} +.sample-library-popup .sample-row strong{font-size:14px}.sample-library-popup .sample-row small{font-size:11px;line-height:1.5} +.sample-library-popup .sample-selection .sample-preview{height:100%;min-height:0;flex:initial} +.sample-library-popup .sample-location{min-width:0;margin:0;padding:8px 10px;background:#eff6fc;border:0}.sample-library-popup .sample-location dl{margin:3px 0}.sample-library-popup .sample-coordinate-basis{display:none} +.sample-library-popup .sample-section-title h3{font-size:15px} +.sample-library-popup .sample-project-action{padding-top:14px;border-top:1px solid #dce6f0} +.sample-library-popup .sample-project-action .sample-load{min-height:46px;font-size:14px;margin-top:12px} +.sample-library-popup .sample-project-action .sample-selection-info h3{font-size:18px} +@media(max-width:900px){.sample-library-popup{width:96vw;max-width:96vw;height:94dvh}.sample-library-popup .sample-explorer{padding:18px;gap:12px;grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.sample-library-popup .sample-list-section{padding-left:12px}.sample-library-popup .sample-list{grid-template-columns:minmax(0,1fr)}.landing-white .landing-cards{gap:12px;width:95%}.landing-white .landing-card{padding:12px}} +@media(max-width:600px){.sample-library-popup{overflow:auto}.sample-library-popup .sample-explorer{height:auto;display:flex;flex-direction:column}.sample-library-popup .sample-map-section{height:260px;flex-shrink:0}.sample-library-popup .sample-selection{min-height:280px}.sample-library-popup .sample-list-section{height:420px;flex-shrink:0;border-left:0;padding-left:0}.sample-library-popup .sample-list{grid-template-columns:repeat(2,minmax(0,1fr))}.landing-white .landing-cards{grid-template-columns:1fr}} + +@media(max-width:1100px){.sample-library-popup .sample-explorer-head{grid-template-columns:minmax(0,1fr) minmax(240px,300px) auto;gap:12px}.sample-library-popup .sample-explorer{grid-template-columns:minmax(0,1fr) 280px;gap:14px}.sample-library-popup .sample-list-section{padding-left:14px}.sample-library-popup .sample-explorer-head h2{font-size:18px}} +@media(max-width:700px){.sample-library-popup .sample-explorer-head{grid-template-columns:1fr auto}.sample-library-popup .sample-location{grid-column:1/-1;grid-row:2}.sample-library-popup .sample-explorer{grid-template-columns:minmax(0,1fr) 220px}} +@media(max-width:600px){.sample-library-popup .sample-selection{height:260px;min-height:260px;flex-shrink:0}} + +/* Location details sit beside the library title, directly above the left map. */ +@media(min-width:701px){ + .sample-library-popup .sample-explorer-head{position:relative;display:flex;min-height:112px;align-items:flex-start;padding-top:12px} + .sample-library-popup .sample-explorer-head>div:first-child{max-width:310px} + .sample-library-popup .sample-explorer-head>b{margin-left:auto} + .sample-library-popup .sample-explorer-head .sample-location{position:absolute;left:29%;right:calc(32.5% + 24px);bottom:0;width:auto;padding:6px 9px} +} + +/* Compact location overlay stays inside the map without consuming a grid row. */ +.sample-library-popup .sample-explorer-head{min-height:0;align-items:center;padding-top:0} +.sample-library-popup .sample-map .sample-location{position:absolute;top:10px;left:10px;right:auto;bottom:auto;z-index:5;width:min(340px,calc(100% - 60px));padding:8px 10px;margin:0;border:1px solid #d6e5f1;border-radius:8px;background:#ffffffed;box-shadow:0 3px 12px #14263d14;pointer-events:none;grid-column:auto;grid-row:auto} +.sample-library-popup .sample-map .sample-location-heading{margin:0} +.sample-library-popup .sample-map .sample-location p{margin:3px 0} + +.sample-library-popup .sample-map .sample-location{width:max-content;max-width:calc(100% - 60px);padding:7px 10px;background:rgba(248,252,255,.88);border:1px solid rgba(187,209,226,.7);border-radius:8px;box-shadow:0 2px 9px #234a6814;backdrop-filter:blur(6px);text-shadow:none} +.sample-library-popup .sample-map .sample-location dl{grid-template-columns:max-content minmax(0,1fr);column-gap:8px}