🚀. Socket Launch Week Day 3:Socket Firewall Now Blocks Malicious VS Code and Open VSX Extensions.Learn more
Sign In

@coldsurf/shared-utils

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@coldsurf/shared-utils - npm Package Compare versions

Comparing version
1.3.1
to
1.4.0
+1
-1
dist/index.cjs

@@ -1,1 +0,1 @@

var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`zod`)),l=s(require(`jwt-decode`)),u=s(require(`date-fns`)),d=s(require(`slugify`)),f=s(require(`date-fns-tz`)),p=s(require(`date-fns/locale`)),m=`1632802589`,h=`https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${m}`,g=`COLDSURF`,_=`com.fstvllife.android`,v=`https://play.google.com/store/apps/details?id=com.fstvllife.android`,y=`COLDSURF`,b={INSTAGRAM:`https://www.instagram.com/coldsurf.io`,X:`https://x.com/coldsurf_io`},x=`https://coldsurf.io`,S=c.z.union([c.z.literal(`google`),c.z.literal(`apple`),c.z.literal(`email`)]);function C(e){let t=document.createElement(`input`);t.type=`file`,t.click(),t.onchange=async n=>{await e(n),t.remove()}}const w=e=>{try{let t=(0,l.jwtDecode)(e);return t}catch(e){return console.error(`Error decoding JWT:`,e),null}};function T(e){return{"@context":`https://schema.org`,"@type":`MusicEvent`,url:e.url,name:e.name,startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:[{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}}],image:e.images,description:e.description,offers:e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:e.url,validFrom:e.validFrom,name:e.name})),organizer:{"@type":`Organization`,name:e.venue.name}}}var E=class{baseData;constructor({baseData:e}){this.baseData=e}generateMetadata(e){let{icons:t,metadataBase:n,appLinks:r,keywords:i,twitter:a,openGraph:o}=this.baseData,s=[{url:`https://coldsurf.io/icons/favicon.ico`}],c={...e,icons:t,metadataBase:n,appLinks:r,keywords:[...e.keywords??[],...i],twitter:a,openGraph:{siteName:o?.siteName,images:Array.isArray(o?.images)&&o.images.length>0?o.images:s,title:o?.title,description:o?.description,...e.openGraph},locale:`ko`};return c}generateLdJson(e){switch(e.type){case`WebSite`:return{"@context":`https://schema.org`,"@type":`WebSite`,url:e.url,name:e.name};case`MusicEvent`:return T(e);case`Brand`:return{"@context":`https://schema.org`,"@type":`Brand`,name:e.name,image:e.image,logo:e.logo,url:e.url,sameAs:e.sameAs};case`Place`:return{"@context":`https://schema.org`,"@type":`Place`,address:e.address,event:e.events.map(e=>T(e)),geo:{"@type":`GeoCoordinates`,latitude:e.latitude,longitude:e.longitude},name:e.name,url:e.url,description:e.description};case`PerformingGroup`:return{"@context":`https://schema.org`,"@type":`PerformingGroup`,image:e.image,name:e.name,url:e.url,event:e.events.map(e=>T(e))};case`WebPageAbout`:return{"@context":`https://schema.org`,name:e.name,"@type":`AboutPage`,url:e.url,image:e.image,sameAs:e.sameAs,description:e.description};default:return{}}}};function D(e,t){let n=Math.ceil(e),r=Math.floor(t);return Math.floor(Math.random()*(r-n+1))+n}function O(){let e=new Date().getTime(),t=typeof performance<`u`&&performance.now&&performance.now()*1e3||0;return`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,n=>{let r=Math.random()*16;return e>0?(r=(e+r)%16|0,e=Math.floor(e/16)):(r=(t+r)%16|0,t=Math.floor(t/16)),(n===`x`?r:r&3|8).toString(16)})}function k(e,{silent:t=!0,fallback:n}={}){try{return JSON.parse(e)}catch(r){return t||console.warn(`JSON parse error, return fallback:`,r,`| input:`,e),n}}const A=e=>(0,d.default)(e,{replacement:`-`,lower:!0,strict:!1,remove:/[[\]*+~.()'"?!:@,&<>〈〉#]/g});async function j(e,t){let n=I(e),r=await t(n);if(r){let e=1,i;do i=`${n}-${e}`,r=await t(i),e++;while(r);n=i}return n}const M=[[/#/g,`no`],[/&/g,`and`],[/%/g,`percent`]];function N(e){return M.reduce((e,[t,n])=>e.replace(t,n),e)}function P(e){if(e>3&&e<21)return`th`;switch(e%10){case 1:return`st`;case 2:return`nd`;case 3:return`rd`;default:return`th`}}function F(e){let t=Number((0,u.format)(e,`d`)),n=(0,u.format)(e,`MMM`).toLowerCase(),r=P(t);return`${t}${r}-${n}`}const I=e=>{let t=(0,d.default)(N(`${e}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},L=e=>{let t=(0,d.default)(N(`${e}`),{replacement:`_`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},R=({title:e,date:t,venueName:n,area:r})=>{let i=`${e}-${F(t)}`;n&&(i+=`-${n}`),r&&(i+=`-${r}`),i+=`-티켓`;let a=(0,d.default)(N(`${i}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return a};async function z({title:e,date:t,venueName:n,area:r},i){let a=R({title:e,date:t,venueName:n,area:r}),o=await i(a);if(o){let e=1,t;do t=`${a}-${e}`,o=await i(t),e++;while(o);a=t}return a}const B=e=>{switch(e){case`Gigs`:return`콘서트`;case`Theatre`:return`연극 / 뮤지컬`;case`Dance`:return`무용`;case`Korean-Traditional`:return`국악`;case`Classic`:return`클래식`;case`Party`:return`파티 / 오프라인`;case`Dj`:return`디제잉`;default:return e}},V={getEventCategoryUIName:B},H=e=>{switch(e.toLowerCase()){case`seoul`:return`서울`;case`incheon`:return`인천`;case`yeongjongdo`:return`영종도`;case`ulsan`:return`울산`;case`busan`:return`부산`;case`daegu`:return`대구`;case`jeju`:return`제주`;case`gyeongsangbuk-do`:return`경상북도`;case`gyeongsangnam-do`:return`경상남도`;case`gwangju`:return`광주`;case`daejeon`:return`대전`;case`sejong-city`:return`세종시`;case`gyeonggi-do`:return`경기도`;case`gangwon-do`:return`강원도`;case`chungcheongbuk-do`:return`충청북도`;case`chungcheongnam-do`:return`충청남도`;case`jeollabuk-do`:return`전라북도`;case`jeollanam-do`:return`전라남도`;case`tokyo`:return`도쿄`;case`osaka`:return`오사카`;case`hochiminh`:return`호치민`;default:return e}},U={getLocationCityUIName:H},W=`Asia/Seoul`;function G(e,t=new Date){return!(0,u.isSameYear)(e,t)}function K(e,t){let n=(0,f.toZonedTime)(e,W),r=n.getMinutes();if(t?.formatStyle===`english`){let e=(()=>G(n)?`MMM dd, yyyy, h:mm a`:`MMM dd, h:mm a`)();return(0,u.format)(n,e,{locale:p.enUS})}let i=(()=>G(n)?r===0?`EEEE a h시, yyyy년 MMMM d일`:`EEEE a h시 m분, yyyy년 MMMM d일`:r===0?`EEEE a h시, MMMM d일`:`EEEE a h시 m분, MMMM d일`)();return(0,u.format)(n,i,{locale:p.ko})}function q({yyyymmdd:e}){if(!/^\d{8}$/.test(e))throw Error(`Invalid date format. Expected YYYYMMDD`);let t=(0,u.parse)(e,`yyyyMMdd`,new Date),n=(0,u.addDays)(t,1),r=(0,f.fromZonedTime)(t,W),i=(0,f.fromZonedTime)(n,W);return[r,i]}function J(e){let t=e?.yyyymmdd?(0,u.parse)(e.yyyymmdd,`yyyyMMdd`,new Date):new Date,n=(0,u.getDay)(t),r=(5-n+7)%7,i=(0,u.startOfDay)((0,u.addDays)(t,r)),a=(0,u.addDays)(i,1),o=(0,u.addDays)(i,2),s=(0,u.addDays)(i,3);return[{label:`FRIDAY`,utcStart:(0,f.fromZonedTime)(i,W),utcEnd:(0,f.fromZonedTime)(a,W)},{label:`SATURDAY`,utcStart:(0,f.fromZonedTime)(a,W),utcEnd:(0,f.fromZonedTime)(o,W)},{label:`SUNDAY`,utcStart:(0,f.fromZonedTime)(o,W),utcEnd:(0,f.fromZonedTime)(s,W)}]}const Y={parseEventDate:K,toUTCDayRangeFromYYYYMMDD:q,getWeekendUTCStartDates:J};function X(e){let t=e,n=``;for(;t!==n;){n=t;try{t=decodeURIComponent(t)}catch{break}}return t}function Z(e){try{let t=new URL(e);return t.pathname=X(t.pathname),t.toString()}catch{return X(e)}}function Q(e){return e.includes(`%`)}function $(e){return/%25(25)+/i.test(e)}exports.APP_STORE_ID=m,exports.APP_STORE_URL=h,exports.COLDSURF_WEB_URL=x,exports.NextMetadataGenerator=E,exports.PLAYSTORE_APP_NAME=y,exports.PLAYSTORE_PACKAGE=_,exports.PLAYSTORE_URL=v,exports.SERVICE_NAME=g,exports.SNS_LINKS=b,exports.createConcertSlug=R,exports.createSlug=I,exports.createSlugHashtag=L,exports.dateUtils=Y,exports.decodeJwt=w,exports.eventCategoryUtils=V,exports.fullyDecodePathname=Z,exports.fullyDecodeURI=X,exports.generateConcertSlug=z,exports.generateSlug=j,exports.generateUUID=O,exports.getRandomInt=D,exports.getSafeSlug=A,exports.isDoubleEncoded=$,exports.isEncoded=Q,exports.locationCityUtils=U,exports.loginProviderSchema=S,exports.pickFile=C,exports.tryParse=k;
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`zod`)),l=s(require(`jwt-decode`)),u=s(require(`date-fns`)),d=s(require(`slugify`)),f=s(require(`date-fns-tz`)),p=s(require(`date-fns/locale`)),m=`1632802589`,h=`https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${m}`,ee=`COLDSURF`,te=`com.fstvllife.android`,g=`https://play.google.com/store/apps/details?id=com.fstvllife.android`,_=`COLDSURF`,v={INSTAGRAM:`https://www.instagram.com/coldsurf.io`,X:`https://x.com/coldsurf_io`},y=`https://coldsurf.io`,b={ko:`ko_KR`,en:`en_US`},x=`https://schema.org`;function ne(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function S(e,t){let n=e.replace(/\/$/,``),r=t.startsWith(`/`)?t:`/${t}`;return`${n}${r}`}function C(e,t){return t.startsWith(`http`)?t:S(e,t)}function re(e,t,n){return!t||n?e:`${e}${e.includes(`?`)?`&`:`?`}v=${encodeURIComponent(t)}`}function w(e,t){return C(e,t)}function T(e,t){let n=e.publisher;return n?{"@type":`Organization`,"@id":t,name:n.name,url:n.url??e.baseUrl.replace(/\/$/,``),logo:n.logoPath?S(e.baseUrl,n.logoPath):void 0,sameAs:n.sameAs}:null}function E(e,t){let n=e.editor;return n?{"@type":`Person`,"@id":t,name:n.name,url:n.url,sameAs:n.sameAs}:null}function D(e,t,n,r){return{"@type":`WebSite`,"@id":n,name:e.name,url:e.baseUrl.replace(/\/$/,``),inLanguage:t.lang,publisher:e.publisher?{"@id":r}:void 0}}function O(e,t,n,r,i){let a=e.article;return a?{"@type":`BlogPosting`,headline:e.title,description:e.description,url:n.url,mainEntityOfPage:n.url,inLanguage:n.lang,datePublished:a.publishedTime,dateModified:a.modifiedTime,image:n.imageUrl,articleSection:a.section,keywords:a.tags,author:t.editor?{"@id":i}:a.author?{"@type":`Person`,name:a.author}:void 0,publisher:t.publisher?{"@id":r}:void 0}:null}function k(e,t){let n=e.name,r=e.image?C(t,e.image):void 0,i=e.url?w(t,e.url):void 0,a=e.sameAs?.length?e.sameAs:void 0,o=e.year==null?void 0:String(e.year),s=e.by?{"@type":`MusicGroup`,name:e.by}:void 0,c=e.by?{"@type":`Person`,name:e.by}:void 0;switch(e.kind){case`albums`:return{"@type":`MusicAlbum`,name:n,byArtist:s,image:r,url:i,sameAs:a,datePublished:o};case`tracks`:return{"@type":`MusicRecording`,name:n,byArtist:s,image:r,url:i,sameAs:a};case`concerts`:return{"@type":`MusicEvent`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`events`:return{"@type":`Event`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`films`:return{"@type":`Movie`,name:n,director:c,image:r,url:i,sameAs:a,datePublished:o};case`books`:return{"@type":`Book`,name:n,author:c,image:r,url:i,sameAs:a,datePublished:o}}}function A(e,t,n,r,i,a,o,s){let c=n.article;return{"@type":`Review`,name:i.fullTitle,reviewBody:n.description,url:i.url,inLanguage:i.lang,datePublished:c?.publishedTime,author:r.editor?{"@id":s}:c?.author?{"@type":`Person`,name:c.author}:void 0,publisher:r.publisher?{"@id":o}:void 0,itemReviewed:k(e,a),reviewRating:t?{"@type":`Rating`,ratingValue:t.value,bestRating:t.best,worstRating:t.worst}:void 0}}function j(e,t){return{"@type":`BreadcrumbList`,itemListElement:e.map((e,n)=>({"@type":`ListItem`,position:n+1,name:e.name,item:w(t,e.path)}))}}function M(e,t){return{"@type":`MusicEvent`,name:e.name,url:w(t,e.url),startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}},image:e.images?.length?e.images.map(e=>C(t,e)):void 0,description:e.description,offers:e.offers?.length?e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:C(t,e.url),validFrom:e.validFrom,name:e.name})):void 0,organizer:{"@type":`Organization`,name:e.organizer??e.venue.name}}}function N(e,t,n){let r=e.jsonLd??[];if(r.length===0)return[];let i=t.baseUrl.replace(/\/$/,``),a=`${i}/#org`,o=`${i}/#editor`,s=`${i}/#website`,c=[];for(let l of r)switch(l.type){case`WebSite`:c.push(D(t,n,s,a));break;case`Article`:{let r=O(e,t,n,a,o);r&&c.push(r);break}case`Review`:c.push(A(l.itemReviewed,l.rating,e,t,n,i,a,o));break;case`CreativeWork`:c.push(k(l.item,i));break;case`EventPage`:c.push(M(l.event,i));break;case`Breadcrumb`:c.push(j(l.items,i));break}if(c.length===0)return[];let l=[],u=T(t,a);u&&l.push(u);let d=E(t,o);d&&l.push(d);let f={"@context":x,"@graph":[...l,...c]};return[{type:`application/ld+json`,innerHTML:JSON.stringify(f)}]}function P(e,t){let n=e.lang??`ko`,r=RegExp(`\\b${ne(t.name)}\\b`).test(e.title),i=r?e.title:`${e.title} | ${t.name}`,a=w(t.baseUrl,e.path),o=e.alternates??[],s={...b,...t.locales},c=[{rel:`canonical`,href:a}];if(o.length>0){c.push({rel:`alternate`,hreflang:n,href:a});for(let e of o)c.push({rel:`alternate`,hreflang:e.lang,href:w(t.baseUrl,e.path)});let r=o.find(e=>e.lang===`ko`)?.path??e.path;c.push({rel:`alternate`,hreflang:`x-default`,href:w(t.baseUrl,r)})}let l=[{name:`description`,content:e.description},{name:`robots`,content:`index, follow`},{property:`og:title`,content:i},{property:`og:description`,content:e.description},{property:`og:type`,content:e.article?`article`:`website`},{property:`og:url`,content:a},{property:`og:site_name`,content:t.name},{property:`og:locale`,content:s[n]}],u=[...new Set([...e.keywords??[],...t.keywords??[]])];u.length>0&&l.push({name:`keywords`,content:u.join(`, `)});let d=e.image??t.defaultImage,f=d?re(C(t.baseUrl,d.path),t.assetVersion,d.path.startsWith(`http`)):void 0;d&&f&&(l.push({property:`og:image`,content:f}),d.type&&l.push({property:`og:image:type`,content:d.type}),d.width&&l.push({property:`og:image:width`,content:String(d.width)}),d.height&&l.push({property:`og:image:height`,content:String(d.height)}),d.alt&&l.push({property:`og:image:alt`,content:d.alt}),l.push({name:`twitter:image`,content:f}),d.alt&&l.push({name:`twitter:image:alt`,content:d.alt})),t.logoPath&&l.push({property:`og:logo`,content:S(t.baseUrl,t.logoPath)});let p=!!d&&!!d.width&&!!d.height&&d.width>=d.height*1.5,m=p?`summary_large_image`:`summary`;l.push({name:`twitter:card`,content:m},{name:`twitter:title`,content:i},{name:`twitter:description`,content:e.description});for(let e of o)e.lang!==n&&l.push({property:`og:locale:alternate`,content:s[e.lang]});if(e.article){l.push({property:`article:published_time`,content:e.article.publishedTime}),e.article.modifiedTime&&l.push({property:`article:modified_time`,content:e.article.modifiedTime}),e.article.author&&l.push({property:`article:author`,content:e.article.author}),e.article.section&&l.push({property:`article:section`,content:e.article.section});for(let t of e.article.tags??[])l.push({property:`article:tag`,content:t})}t.appLinks?.ios&&(l.push({property:`al:ios:app_store_id`,content:t.appLinks.ios.appStoreId}),l.push({property:`al:ios:app_name`,content:t.appLinks.ios.appName}),e.appLinks?.iosUrl&&l.push({property:`al:ios:url`,content:e.appLinks.iosUrl})),t.appLinks?.android&&(l.push({property:`al:android:package`,content:t.appLinks.android.packageName}),t.appLinks.android.appName&&l.push({property:`al:android:app_name`,content:t.appLinks.android.appName}),e.appLinks?.androidUrl&&l.push({property:`al:android:url`,content:e.appLinks.androidUrl}));let h=N(e,t,{url:a,fullTitle:i,imageUrl:f,lang:n});return{title:i,htmlLang:n,meta:l,link:c,script:h}}function F(e){return{buildTags:t=>P(t,e)}}const I=c.z.union([c.z.literal(`google`),c.z.literal(`apple`),c.z.literal(`email`)]);function L(e){let t=document.createElement(`input`);t.type=`file`,t.click(),t.onchange=async n=>{await e(n),t.remove()}}const R=e=>{try{let t=(0,l.jwtDecode)(e);return t}catch(e){return console.error(`Error decoding JWT:`,e),null}};function z(e,t){let n=Math.ceil(e),r=Math.floor(t);return Math.floor(Math.random()*(r-n+1))+n}function B(){let e=new Date().getTime(),t=typeof performance<`u`&&performance.now&&performance.now()*1e3||0;return`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,n=>{let r=Math.random()*16;return e>0?(r=(e+r)%16|0,e=Math.floor(e/16)):(r=(t+r)%16|0,t=Math.floor(t/16)),(n===`x`?r:r&3|8).toString(16)})}function V(e,{silent:t=!0,fallback:n}={}){try{return JSON.parse(e)}catch(r){return t||console.warn(`JSON parse error, return fallback:`,r,`| input:`,e),n}}const H=e=>(0,d.default)(e,{replacement:`-`,lower:!0,strict:!1,remove:/[[\]*+~.()'"?!:@,&<>〈〉#]/g});async function U(e,t){let n=q(e),r=await t(n);if(r){let e=1,i;do i=`${n}-${e}`,r=await t(i),e++;while(r);n=i}return n}const ie=[[/#/g,`no`],[/&/g,`and`],[/%/g,`percent`]];function W(e){return ie.reduce((e,[t,n])=>e.replace(t,n),e)}function G(e){if(e>3&&e<21)return`th`;switch(e%10){case 1:return`st`;case 2:return`nd`;case 3:return`rd`;default:return`th`}}function K(e){let t=Number((0,u.format)(e,`d`)),n=(0,u.format)(e,`MMM`).toLowerCase(),r=G(t);return`${t}${r}-${n}`}const q=e=>{let t=(0,d.default)(W(`${e}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},J=e=>{let t=(0,d.default)(W(`${e}`),{replacement:`_`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},Y=({title:e,date:t,venueName:n,area:r})=>{let i=`${e}-${K(t)}`;n&&(i+=`-${n}`),r&&(i+=`-${r}`),i+=`-티켓`;let a=(0,d.default)(W(`${i}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return a};async function ae({title:e,date:t,venueName:n,area:r},i){let a=Y({title:e,date:t,venueName:n,area:r}),o=await i(a);if(o){let e=1,t;do t=`${a}-${e}`,o=await i(t),e++;while(o);a=t}return a}const oe=e=>{switch(e){case`Gigs`:return`콘서트`;case`Theatre`:return`연극 / 뮤지컬`;case`Dance`:return`무용`;case`Korean-Traditional`:return`국악`;case`Classic`:return`클래식`;case`Party`:return`파티 / 오프라인`;case`Dj`:return`디제잉`;default:return e}},se={getEventCategoryUIName:oe},ce=e=>{switch(e.toLowerCase()){case`seoul`:return`서울`;case`incheon`:return`인천`;case`yeongjongdo`:return`영종도`;case`ulsan`:return`울산`;case`busan`:return`부산`;case`daegu`:return`대구`;case`jeju`:return`제주`;case`gyeongsangbuk-do`:return`경상북도`;case`gyeongsangnam-do`:return`경상남도`;case`gwangju`:return`광주`;case`daejeon`:return`대전`;case`sejong-city`:return`세종시`;case`gyeonggi-do`:return`경기도`;case`gangwon-do`:return`강원도`;case`chungcheongbuk-do`:return`충청북도`;case`chungcheongnam-do`:return`충청남도`;case`jeollabuk-do`:return`전라북도`;case`jeollanam-do`:return`전라남도`;case`tokyo`:return`도쿄`;case`osaka`:return`오사카`;case`hochiminh`:return`호치민`;default:return e}},le={getLocationCityUIName:ce},X=`Asia/Seoul`;function Z(e,t=new Date){return!(0,u.isSameYear)(e,t)}function ue(e,t){let n=(0,f.toZonedTime)(e,X),r=n.getMinutes();if(t?.formatStyle===`english`){let e=(()=>Z(n)?`MMM dd, yyyy, h:mm a`:`MMM dd, h:mm a`)();return(0,u.format)(n,e,{locale:p.enUS})}let i=(()=>Z(n)?r===0?`EEEE a h시, yyyy년 MMMM d일`:`EEEE a h시 m분, yyyy년 MMMM d일`:r===0?`EEEE a h시, MMMM d일`:`EEEE a h시 m분, MMMM d일`)();return(0,u.format)(n,i,{locale:p.ko})}function Q({yyyymmdd:e}){if(!/^\d{8}$/.test(e))throw Error(`Invalid date format. Expected YYYYMMDD`);let t=(0,u.parse)(e,`yyyyMMdd`,new Date),n=(0,u.addDays)(t,1),r=(0,f.fromZonedTime)(t,X),i=(0,f.fromZonedTime)(n,X);return[r,i]}function de(e){let t=e?.yyyymmdd?(0,u.parse)(e.yyyymmdd,`yyyyMMdd`,new Date):new Date,n=(0,u.getDay)(t),r=(5-n+7)%7,i=(0,u.startOfDay)((0,u.addDays)(t,r)),a=(0,u.addDays)(i,1),o=(0,u.addDays)(i,2),s=(0,u.addDays)(i,3);return[{label:`FRIDAY`,utcStart:(0,f.fromZonedTime)(i,X),utcEnd:(0,f.fromZonedTime)(a,X)},{label:`SATURDAY`,utcStart:(0,f.fromZonedTime)(a,X),utcEnd:(0,f.fromZonedTime)(o,X)},{label:`SUNDAY`,utcStart:(0,f.fromZonedTime)(o,X),utcEnd:(0,f.fromZonedTime)(s,X)}]}const fe={parseEventDate:ue,toUTCDayRangeFromYYYYMMDD:Q,getWeekendUTCStartDates:de};function $(e){let t=e,n=``;for(;t!==n;){n=t;try{t=decodeURIComponent(t)}catch{break}}return t}function pe(e){try{let t=new URL(e);return t.pathname=$(t.pathname),t.toString()}catch{return $(e)}}function me(e){return e.includes(`%`)}function he(e){return/%25(25)+/i.test(e)}exports.APP_STORE_ID=m,exports.APP_STORE_URL=h,exports.COLDSURF_WEB_URL=y,exports.PLAYSTORE_APP_NAME=_,exports.PLAYSTORE_PACKAGE=te,exports.PLAYSTORE_URL=g,exports.SERVICE_NAME=ee,exports.SNS_LINKS=v,exports.buildSeoTags=P,exports.createConcertSlug=Y,exports.createSeo=F,exports.createSlug=q,exports.createSlugHashtag=J,exports.dateUtils=fe,exports.decodeJwt=R,exports.eventCategoryUtils=se,exports.fullyDecodePathname=pe,exports.fullyDecodeURI=$,exports.generateConcertSlug=ae,exports.generateSlug=U,exports.generateUUID=B,exports.getRandomInt=z,exports.getSafeSlug=H,exports.isDoubleEncoded=he,exports.isEncoded=me,exports.locationCityUtils=le,exports.loginProviderSchema=I,exports.pickFile=L,exports.tryParse=V;

@@ -29,76 +29,67 @@ import { z } from "zod";

//#endregion
//#region src/types/auth.d.ts
declare const loginProviderSchema: z.ZodUnion<[z.ZodLiteral<"google">, z.ZodLiteral<"apple">, z.ZodLiteral<"email">]>;
type LoginProvider = z.infer<typeof loginProviderSchema>;
//# sourceMappingURL=auth.d.ts.map
//#endregion
//#region src/utils/utils.file.d.ts
declare function pickFile(onChange: (e: Event) => void | Promise<void>): void;
//# sourceMappingURL=utils.file.d.ts.map
//#endregion
//#region src/utils/utils.jwt.d.ts
interface JwtPayload {
sub: string;
//#region src/metadata/core.d.ts
type Lang = 'ko' | 'en';
type SeoImage = {
/** Absolute URL or path starting with `/`. */
path: string;
width?: number;
height?: number;
type?: string;
alt?: string;
};
type ArticleMeta = {
/** ISO 8601 timestamp. */
publishedTime: string;
/** ISO 8601 timestamp. */
modifiedTime?: string;
author?: string;
section?: string;
tags?: string[];
};
/**
* apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다.
* `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등).
*/
type CreativeWorkKind = 'albums' | 'tracks' | 'concerts' | 'films' | 'books' | 'events';
/** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */
type JsonLdWork = {
kind: CreativeWorkKind;
name: string;
email: string;
exp: number;
[key: string]: unknown;
}
declare const decodeJwt: (token: string) => JwtPayload | null;
//#endregion
//#region src/utils/utils.metadata.d.ts
type BaseData = {
keywords: string[];
icons: {
icon: string;
shortcut: string;
apple: string;
};
metadataBase: URL;
appLinks?: {
ios?: {
app_name: string;
app_store_id: string;
url: string;
};
android?: {
package: string;
url?: string | URL;
class?: string;
app_name?: string;
};
};
twitter?: {
app?: {
id: {
/**
* app store id
*/
iphone: string;
};
name: string;
url: {
/**
* app store url
*/
iphone: string;
};
};
card: 'app';
};
openGraph?: {
siteName: string;
title: string;
description: string;
images?: {
url: string;
}[];
};
/** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */
by?: string;
/** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */
year?: number | string;
/** Path(`/...`) 또는 절대 URL. */
image?: string;
/** 작품 페이지 path(`/...`) 또는 절대 URL. */
url?: string;
/** 외부 출처 — bandcamp · spotify · imdb 등. */
sameAs?: string[];
};
type CustomMusicEvent = {
type: 'MusicEvent';
type JsonLdRating = {
value: number;
best?: number;
worst?: number;
};
type JsonLdEventOffer = {
price: number;
currency: string;
/** Path(`/...`) 또는 절대 URL. */
url: string;
/** ISO 8601 — 티켓 오픈 시각. */
validFrom: string;
name?: string;
};
/**
* 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와
* 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다.
*/
type JsonLdEvent = {
name: string;
/** 이벤트 페이지 path(`/...`) 또는 절대 URL. */
url: string;
/** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */
startDate: string;
endDate: string;
/** ISO 8601 */
endDate?: string;
venue: {

@@ -110,233 +101,165 @@ name: string;

};
images: string[];
/** Path(`/...`) 또는 절대 URL 목록. */
images?: string[];
description?: string;
offers?: JsonLdEventOffer[];
/** 주최자명. 미지정 시 `venue.name` 으로 대체. */
organizer?: string;
};
/**
* 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다.
* publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다.
*/
type JsonLd = {
type: 'WebSite';
}
/** `input.article` 에서 파생 — article 없으면 무시. */ | {
type: 'Article';
} | {
type: 'Review';
itemReviewed: JsonLdWork;
rating?: JsonLdRating;
} | {
type: 'CreativeWork';
item: JsonLdWork;
}
/** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */ | {
type: 'EventPage';
event: JsonLdEvent;
} | {
type: 'Breadcrumb';
items: {
name: string;
path: string;
}[];
};
type PageSeoInput = {
title: string;
description: string;
offers: {
price: number;
currency: string;
url: string;
validFrom: string;
name?: string;
path: string;
lang?: Lang;
/** Alternate language versions for this page. Emits hreflang link tags. */
alternates?: {
lang: Lang;
path: string;
}[];
/** When set, og:type switches to `article` and article:* tags are emitted. */
article?: ArticleMeta;
/** Override the default OG/Twitter share image for this page. */
image?: SeoImage;
/** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */
keywords?: string[];
/** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */
jsonLd?: JsonLd[];
/**
* Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks`
* 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시.
*/
appLinks?: {
iosUrl?: string;
androidUrl?: string;
};
};
declare class NextMetadataGenerator {
baseData: BaseData;
constructor({
baseData
}: {
baseData: BaseData;
});
generateMetadata<T>(additionalMetadata: T): T;
generateLdJson(params: {
type: 'WebSite';
url: string;
type SiteConfig = {
/** Display name appended to every page title. */
name: string;
/** Origin used to resolve absolute URLs. Trailing slash is normalized. */
baseUrl: string;
/** Default OG/Twitter image used when a page does not override it. */
defaultImage?: SeoImage;
/** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */
logoPath?: string;
/** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */
locales?: Partial<Record<Lang, string>>;
/** Site-wide keywords merged into every page's `keywords` meta. */
keywords?: string[];
/**
* Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter
* image URLs (paths, not external `http` URLs). Bump it when an image is replaced
* in place under the same filename so social scrapers (Threads/Slack/iMessage 등)
* treat it as a new URL instead of serving their stale cache.
*/
assetVersion?: string;
/** Publishing organization — emitted as the `Organization` JSON-LD node. */
publisher?: {
name: string;
} | CustomMusicEvent | {
type: 'Brand';
url?: string;
logoPath?: string;
sameAs?: string[];
};
/** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */
editor?: {
name: string;
image: string;
logo: string;
url: string;
sameAs: string[];
} | {
type: 'Place';
address: string;
latitude: number;
longitude: number;
name: string;
url: string;
events: CustomMusicEvent[];
description: string;
} | {
type: 'PerformingGroup';
image: string;
name: string;
url: string;
events: CustomMusicEvent[];
} | {
type: 'WebPageAbout';
name: string;
url: string;
image: string;
sameAs: string[];
description: string;
}): {
'@context': "https://schema.org";
'@type': "MusicEvent";
url: string;
name: string;
startDate: string;
endDate: string;
eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode";
eventStatus: "https://schema.org/EventScheduled";
location: {
'@type': "Place";
name: string;
address: string;
geo: {
'@type': "GeoCoordinates";
latitude: number;
longitude: number;
};
}[];
image: string[];
description: string;
offers: {
'@type': "Offer";
availability: "https://schema.org/InStock";
price: number;
priceCurrency: string;
url: string;
validFrom: string;
name: string | undefined;
}[];
organizer: {
'@type': "Organization";
name: string;
url?: string;
sameAs?: string[];
};
/**
* 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit.
* 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다
* (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다.
*/
appLinks?: {
ios?: {
appStoreId: string;
appName: string;
};
} | {
'@context': "https://schema.org";
'@type': "WebSite";
url: string;
name: string;
image?: undefined;
logo?: undefined;
sameAs?: undefined;
address?: undefined;
event?: undefined;
geo?: undefined;
description?: undefined;
} | {
'@context': "https://schema.org";
'@type': "Brand";
name: string;
image: string;
logo: string;
url: string;
sameAs: string[];
address?: undefined;
event?: undefined;
geo?: undefined;
description?: undefined;
} | {
'@context': "https://schema.org";
'@type': "Place";
address: string;
event: {
'@context': "https://schema.org";
'@type': "MusicEvent";
url: string;
name: string;
startDate: string;
endDate: string;
eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode";
eventStatus: "https://schema.org/EventScheduled";
location: {
'@type': "Place";
name: string;
address: string;
geo: {
'@type': "GeoCoordinates";
latitude: number;
longitude: number;
};
}[];
image: string[];
description: string;
offers: {
'@type': "Offer";
availability: "https://schema.org/InStock";
price: number;
priceCurrency: string;
url: string;
validFrom: string;
name: string | undefined;
}[];
organizer: {
'@type': "Organization";
name: string;
};
}[];
geo: {
'@type': "GeoCoordinates";
latitude: number;
longitude: number;
android?: {
packageName: string;
appName?: string;
};
name: string;
url: string;
description: string;
image?: undefined;
logo?: undefined;
sameAs?: undefined;
} | {
'@context': "https://schema.org";
'@type': "PerformingGroup";
image: string;
name: string;
url: string;
event: {
'@context': "https://schema.org";
'@type': "MusicEvent";
url: string;
name: string;
startDate: string;
endDate: string;
eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode";
eventStatus: "https://schema.org/EventScheduled";
location: {
'@type': "Place";
name: string;
address: string;
geo: {
'@type': "GeoCoordinates";
latitude: number;
longitude: number;
};
}[];
image: string[];
description: string;
offers: {
'@type': "Offer";
availability: "https://schema.org/InStock";
price: number;
priceCurrency: string;
url: string;
validFrom: string;
name: string | undefined;
}[];
organizer: {
'@type': "Organization";
name: string;
};
}[];
logo?: undefined;
sameAs?: undefined;
address?: undefined;
geo?: undefined;
description?: undefined;
} | {
'@context': "https://schema.org";
name: string;
'@type': "AboutPage";
url: string;
image: string;
sameAs: string[];
description: string;
logo?: undefined;
address?: undefined;
event?: undefined;
geo?: undefined;
} | {
'@context'?: undefined;
'@type'?: undefined;
url?: undefined;
name?: undefined;
image?: undefined;
logo?: undefined;
sameAs?: undefined;
address?: undefined;
event?: undefined;
geo?: undefined;
description?: undefined;
};
};
type SeoMeta = {
name?: string;
property?: string;
content: string;
};
type SeoLink = {
rel: string;
href: string;
hreflang?: string;
};
type SeoScript = {
type: 'application/ld+json';
innerHTML: string;
};
type SeoTags = {
title: string;
htmlLang: Lang;
meta: SeoMeta[];
link: SeoLink[];
/** JSON-LD `<script>` tags. Empty unless `input.jsonLd` is set. */
script: SeoScript[];
};
declare function buildSeoTags(input: PageSeoInput, site: SiteConfig): SeoTags;
/**
* `SiteConfig` 를 한 번 바인딩해 페이지 입력만 받는 `buildTags(input)` 를 만든다. 구
* `NextMetadataGenerator` 의 "생성자 1회 주입" 사용감을 stateful 클래스 없이 순수 함수로 재현한다.
*
* const seo = createSeo(siteConfig)
* seo.buildTags({ title, description, path, jsonLd: [...] })
*/
declare function createSeo(site: SiteConfig): {
buildTags: (input: PageSeoInput) => SeoTags;
};
//# sourceMappingURL=core.d.ts.map
//#endregion
//#region src/types/auth.d.ts
declare const loginProviderSchema: z.ZodUnion<[z.ZodLiteral<"google">, z.ZodLiteral<"apple">, z.ZodLiteral<"email">]>;
type LoginProvider = z.infer<typeof loginProviderSchema>;
//# sourceMappingURL=auth.d.ts.map
//#endregion
//#region src/utils/utils.file.d.ts
declare function pickFile(onChange: (e: Event) => void | Promise<void>): void;
//# sourceMappingURL=utils.file.d.ts.map
//#endregion
//#region src/utils/utils.jwt.d.ts
interface JwtPayload {
sub: string;
name: string;
email: string;
exp: number;
[key: string]: unknown;
}
declare const decodeJwt: (token: string) => JwtPayload | null;
//#endregion

@@ -457,3 +380,3 @@ //#region src/utils/utils.number.d.ts

//#endregion
export { APP_STORE_ID, APP_STORE_URL, COLDSURF_WEB_URL, LoginProvider, NextMetadataGenerator, PLAYSTORE_APP_NAME, PLAYSTORE_PACKAGE, PLAYSTORE_URL, SERVICE_NAME, SNS_LINKS, TryParseOptions, createConcertSlug, createSlug, createSlugHashtag, dateUtils, decodeJwt, eventCategoryUtils, fullyDecodePathname, fullyDecodeURI, generateConcertSlug, generateSlug, generateUUID, getRandomInt, getSafeSlug, isDoubleEncoded, isEncoded, locationCityUtils, loginProviderSchema, pickFile, tryParse };
export { APP_STORE_ID, APP_STORE_URL, ArticleMeta, COLDSURF_WEB_URL, CreativeWorkKind, JsonLd, JsonLdEvent, JsonLdEventOffer, JsonLdRating, JsonLdWork, Lang, LoginProvider, PLAYSTORE_APP_NAME, PLAYSTORE_PACKAGE, PLAYSTORE_URL, PageSeoInput, SERVICE_NAME, SNS_LINKS, SeoImage, SeoLink, SeoMeta, SeoScript, SeoTags, SiteConfig, TryParseOptions, buildSeoTags, createConcertSlug, createSeo, createSlug, createSlugHashtag, dateUtils, decodeJwt, eventCategoryUtils, fullyDecodePathname, fullyDecodeURI, generateConcertSlug, generateSlug, generateUUID, getRandomInt, getSafeSlug, isDoubleEncoded, isEncoded, locationCityUtils, loginProviderSchema, pickFile, tryParse };
//# sourceMappingURL=index.d.cts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.cts","names":[],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.metadata.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":[],"mappings":"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"}
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/metadata/core.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":[],"mappings":"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"}

@@ -29,76 +29,67 @@ import { z } from "zod";

//#endregion
//#region src/types/auth.d.ts
declare const loginProviderSchema: z.ZodUnion<[z.ZodLiteral<"google">, z.ZodLiteral<"apple">, z.ZodLiteral<"email">]>;
type LoginProvider = z.infer<typeof loginProviderSchema>;
//# sourceMappingURL=auth.d.ts.map
//#endregion
//#region src/utils/utils.file.d.ts
declare function pickFile(onChange: (e: Event) => void | Promise<void>): void;
//# sourceMappingURL=utils.file.d.ts.map
//#endregion
//#region src/utils/utils.jwt.d.ts
interface JwtPayload {
sub: string;
//#region src/metadata/core.d.ts
type Lang = 'ko' | 'en';
type SeoImage = {
/** Absolute URL or path starting with `/`. */
path: string;
width?: number;
height?: number;
type?: string;
alt?: string;
};
type ArticleMeta = {
/** ISO 8601 timestamp. */
publishedTime: string;
/** ISO 8601 timestamp. */
modifiedTime?: string;
author?: string;
section?: string;
tags?: string[];
};
/**
* apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다.
* `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등).
*/
type CreativeWorkKind = 'albums' | 'tracks' | 'concerts' | 'films' | 'books' | 'events';
/** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */
type JsonLdWork = {
kind: CreativeWorkKind;
name: string;
email: string;
exp: number;
[key: string]: unknown;
}
declare const decodeJwt: (token: string) => JwtPayload | null;
//#endregion
//#region src/utils/utils.metadata.d.ts
type BaseData = {
keywords: string[];
icons: {
icon: string;
shortcut: string;
apple: string;
};
metadataBase: URL;
appLinks?: {
ios?: {
app_name: string;
app_store_id: string;
url: string;
};
android?: {
package: string;
url?: string | URL;
class?: string;
app_name?: string;
};
};
twitter?: {
app?: {
id: {
/**
* app store id
*/
iphone: string;
};
name: string;
url: {
/**
* app store url
*/
iphone: string;
};
};
card: 'app';
};
openGraph?: {
siteName: string;
title: string;
description: string;
images?: {
url: string;
}[];
};
/** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */
by?: string;
/** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */
year?: number | string;
/** Path(`/...`) 또는 절대 URL. */
image?: string;
/** 작품 페이지 path(`/...`) 또는 절대 URL. */
url?: string;
/** 외부 출처 — bandcamp · spotify · imdb 등. */
sameAs?: string[];
};
type CustomMusicEvent = {
type: 'MusicEvent';
type JsonLdRating = {
value: number;
best?: number;
worst?: number;
};
type JsonLdEventOffer = {
price: number;
currency: string;
/** Path(`/...`) 또는 절대 URL. */
url: string;
/** ISO 8601 — 티켓 오픈 시각. */
validFrom: string;
name?: string;
};
/**
* 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와
* 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다.
*/
type JsonLdEvent = {
name: string;
/** 이벤트 페이지 path(`/...`) 또는 절대 URL. */
url: string;
/** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */
startDate: string;
endDate: string;
/** ISO 8601 */
endDate?: string;
venue: {

@@ -110,233 +101,165 @@ name: string;

};
images: string[];
/** Path(`/...`) 또는 절대 URL 목록. */
images?: string[];
description?: string;
offers?: JsonLdEventOffer[];
/** 주최자명. 미지정 시 `venue.name` 으로 대체. */
organizer?: string;
};
/**
* 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다.
* publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다.
*/
type JsonLd = {
type: 'WebSite';
}
/** `input.article` 에서 파생 — article 없으면 무시. */ | {
type: 'Article';
} | {
type: 'Review';
itemReviewed: JsonLdWork;
rating?: JsonLdRating;
} | {
type: 'CreativeWork';
item: JsonLdWork;
}
/** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */ | {
type: 'EventPage';
event: JsonLdEvent;
} | {
type: 'Breadcrumb';
items: {
name: string;
path: string;
}[];
};
type PageSeoInput = {
title: string;
description: string;
offers: {
price: number;
currency: string;
url: string;
validFrom: string;
name?: string;
path: string;
lang?: Lang;
/** Alternate language versions for this page. Emits hreflang link tags. */
alternates?: {
lang: Lang;
path: string;
}[];
/** When set, og:type switches to `article` and article:* tags are emitted. */
article?: ArticleMeta;
/** Override the default OG/Twitter share image for this page. */
image?: SeoImage;
/** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */
keywords?: string[];
/** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */
jsonLd?: JsonLd[];
/**
* Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks`
* 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시.
*/
appLinks?: {
iosUrl?: string;
androidUrl?: string;
};
};
declare class NextMetadataGenerator {
baseData: BaseData;
constructor({
baseData
}: {
baseData: BaseData;
});
generateMetadata<T>(additionalMetadata: T): T;
generateLdJson(params: {
type: 'WebSite';
url: string;
type SiteConfig = {
/** Display name appended to every page title. */
name: string;
/** Origin used to resolve absolute URLs. Trailing slash is normalized. */
baseUrl: string;
/** Default OG/Twitter image used when a page does not override it. */
defaultImage?: SeoImage;
/** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */
logoPath?: string;
/** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */
locales?: Partial<Record<Lang, string>>;
/** Site-wide keywords merged into every page's `keywords` meta. */
keywords?: string[];
/**
* Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter
* image URLs (paths, not external `http` URLs). Bump it when an image is replaced
* in place under the same filename so social scrapers (Threads/Slack/iMessage 등)
* treat it as a new URL instead of serving their stale cache.
*/
assetVersion?: string;
/** Publishing organization — emitted as the `Organization` JSON-LD node. */
publisher?: {
name: string;
} | CustomMusicEvent | {
type: 'Brand';
url?: string;
logoPath?: string;
sameAs?: string[];
};
/** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */
editor?: {
name: string;
image: string;
logo: string;
url: string;
sameAs: string[];
} | {
type: 'Place';
address: string;
latitude: number;
longitude: number;
name: string;
url: string;
events: CustomMusicEvent[];
description: string;
} | {
type: 'PerformingGroup';
image: string;
name: string;
url: string;
events: CustomMusicEvent[];
} | {
type: 'WebPageAbout';
name: string;
url: string;
image: string;
sameAs: string[];
description: string;
}): {
'@context': "https://schema.org";
'@type': "MusicEvent";
url: string;
name: string;
startDate: string;
endDate: string;
eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode";
eventStatus: "https://schema.org/EventScheduled";
location: {
'@type': "Place";
name: string;
address: string;
geo: {
'@type': "GeoCoordinates";
latitude: number;
longitude: number;
};
}[];
image: string[];
description: string;
offers: {
'@type': "Offer";
availability: "https://schema.org/InStock";
price: number;
priceCurrency: string;
url: string;
validFrom: string;
name: string | undefined;
}[];
organizer: {
'@type': "Organization";
name: string;
url?: string;
sameAs?: string[];
};
/**
* 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit.
* 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다
* (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다.
*/
appLinks?: {
ios?: {
appStoreId: string;
appName: string;
};
} | {
'@context': "https://schema.org";
'@type': "WebSite";
url: string;
name: string;
image?: undefined;
logo?: undefined;
sameAs?: undefined;
address?: undefined;
event?: undefined;
geo?: undefined;
description?: undefined;
} | {
'@context': "https://schema.org";
'@type': "Brand";
name: string;
image: string;
logo: string;
url: string;
sameAs: string[];
address?: undefined;
event?: undefined;
geo?: undefined;
description?: undefined;
} | {
'@context': "https://schema.org";
'@type': "Place";
address: string;
event: {
'@context': "https://schema.org";
'@type': "MusicEvent";
url: string;
name: string;
startDate: string;
endDate: string;
eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode";
eventStatus: "https://schema.org/EventScheduled";
location: {
'@type': "Place";
name: string;
address: string;
geo: {
'@type': "GeoCoordinates";
latitude: number;
longitude: number;
};
}[];
image: string[];
description: string;
offers: {
'@type': "Offer";
availability: "https://schema.org/InStock";
price: number;
priceCurrency: string;
url: string;
validFrom: string;
name: string | undefined;
}[];
organizer: {
'@type': "Organization";
name: string;
};
}[];
geo: {
'@type': "GeoCoordinates";
latitude: number;
longitude: number;
android?: {
packageName: string;
appName?: string;
};
name: string;
url: string;
description: string;
image?: undefined;
logo?: undefined;
sameAs?: undefined;
} | {
'@context': "https://schema.org";
'@type': "PerformingGroup";
image: string;
name: string;
url: string;
event: {
'@context': "https://schema.org";
'@type': "MusicEvent";
url: string;
name: string;
startDate: string;
endDate: string;
eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode";
eventStatus: "https://schema.org/EventScheduled";
location: {
'@type': "Place";
name: string;
address: string;
geo: {
'@type': "GeoCoordinates";
latitude: number;
longitude: number;
};
}[];
image: string[];
description: string;
offers: {
'@type': "Offer";
availability: "https://schema.org/InStock";
price: number;
priceCurrency: string;
url: string;
validFrom: string;
name: string | undefined;
}[];
organizer: {
'@type': "Organization";
name: string;
};
}[];
logo?: undefined;
sameAs?: undefined;
address?: undefined;
geo?: undefined;
description?: undefined;
} | {
'@context': "https://schema.org";
name: string;
'@type': "AboutPage";
url: string;
image: string;
sameAs: string[];
description: string;
logo?: undefined;
address?: undefined;
event?: undefined;
geo?: undefined;
} | {
'@context'?: undefined;
'@type'?: undefined;
url?: undefined;
name?: undefined;
image?: undefined;
logo?: undefined;
sameAs?: undefined;
address?: undefined;
event?: undefined;
geo?: undefined;
description?: undefined;
};
};
type SeoMeta = {
name?: string;
property?: string;
content: string;
};
type SeoLink = {
rel: string;
href: string;
hreflang?: string;
};
type SeoScript = {
type: 'application/ld+json';
innerHTML: string;
};
type SeoTags = {
title: string;
htmlLang: Lang;
meta: SeoMeta[];
link: SeoLink[];
/** JSON-LD `<script>` tags. Empty unless `input.jsonLd` is set. */
script: SeoScript[];
};
declare function buildSeoTags(input: PageSeoInput, site: SiteConfig): SeoTags;
/**
* `SiteConfig` 를 한 번 바인딩해 페이지 입력만 받는 `buildTags(input)` 를 만든다. 구
* `NextMetadataGenerator` 의 "생성자 1회 주입" 사용감을 stateful 클래스 없이 순수 함수로 재현한다.
*
* const seo = createSeo(siteConfig)
* seo.buildTags({ title, description, path, jsonLd: [...] })
*/
declare function createSeo(site: SiteConfig): {
buildTags: (input: PageSeoInput) => SeoTags;
};
//# sourceMappingURL=core.d.ts.map
//#endregion
//#region src/types/auth.d.ts
declare const loginProviderSchema: z.ZodUnion<[z.ZodLiteral<"google">, z.ZodLiteral<"apple">, z.ZodLiteral<"email">]>;
type LoginProvider = z.infer<typeof loginProviderSchema>;
//# sourceMappingURL=auth.d.ts.map
//#endregion
//#region src/utils/utils.file.d.ts
declare function pickFile(onChange: (e: Event) => void | Promise<void>): void;
//# sourceMappingURL=utils.file.d.ts.map
//#endregion
//#region src/utils/utils.jwt.d.ts
interface JwtPayload {
sub: string;
name: string;
email: string;
exp: number;
[key: string]: unknown;
}
declare const decodeJwt: (token: string) => JwtPayload | null;
//#endregion

@@ -457,3 +380,3 @@ //#region src/utils/utils.number.d.ts

//#endregion
export { APP_STORE_ID, APP_STORE_URL, COLDSURF_WEB_URL, LoginProvider, NextMetadataGenerator, PLAYSTORE_APP_NAME, PLAYSTORE_PACKAGE, PLAYSTORE_URL, SERVICE_NAME, SNS_LINKS, TryParseOptions, createConcertSlug, createSlug, createSlugHashtag, dateUtils, decodeJwt, eventCategoryUtils, fullyDecodePathname, fullyDecodeURI, generateConcertSlug, generateSlug, generateUUID, getRandomInt, getSafeSlug, isDoubleEncoded, isEncoded, locationCityUtils, loginProviderSchema, pickFile, tryParse };
export { APP_STORE_ID, APP_STORE_URL, ArticleMeta, COLDSURF_WEB_URL, CreativeWorkKind, JsonLd, JsonLdEvent, JsonLdEventOffer, JsonLdRating, JsonLdWork, Lang, LoginProvider, PLAYSTORE_APP_NAME, PLAYSTORE_PACKAGE, PLAYSTORE_URL, PageSeoInput, SERVICE_NAME, SNS_LINKS, SeoImage, SeoLink, SeoMeta, SeoScript, SeoTags, SiteConfig, TryParseOptions, buildSeoTags, createConcertSlug, createSeo, createSlug, createSlugHashtag, dateUtils, decodeJwt, eventCategoryUtils, fullyDecodePathname, fullyDecodeURI, generateConcertSlug, generateSlug, generateUUID, getRandomInt, getSafeSlug, isDoubleEncoded, isEncoded, locationCityUtils, loginProviderSchema, pickFile, tryParse };
//# sourceMappingURL=index.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.ts","names":[],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.metadata.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":[],"mappings":"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"}
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/metadata/core.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":[],"mappings":"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"}

@@ -1,2 +0,2 @@

import{z as e}from"zod";import{jwtDecode as t}from"jwt-decode";import{addDays as n,format as r,getDay as i,isSameYear as a,parse as o,startOfDay as s}from"date-fns";import c from"slugify";import{fromZonedTime as l,toZonedTime as u}from"date-fns-tz";import{enUS as d,ko as f}from"date-fns/locale";const p=`1632802589`,m=`https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${p}`,h=`COLDSURF`,g=`com.fstvllife.android`,_=`https://play.google.com/store/apps/details?id=com.fstvllife.android`,v=`COLDSURF`,y={INSTAGRAM:`https://www.instagram.com/coldsurf.io`,X:`https://x.com/coldsurf_io`},b=`https://coldsurf.io`,x=e.union([e.literal(`google`),e.literal(`apple`),e.literal(`email`)]);function S(e){let t=document.createElement(`input`);t.type=`file`,t.click(),t.onchange=async n=>{await e(n),t.remove()}}const C=e=>{try{let n=t(e);return n}catch(e){return console.error(`Error decoding JWT:`,e),null}};function w(e){return{"@context":`https://schema.org`,"@type":`MusicEvent`,url:e.url,name:e.name,startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:[{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}}],image:e.images,description:e.description,offers:e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:e.url,validFrom:e.validFrom,name:e.name})),organizer:{"@type":`Organization`,name:e.venue.name}}}var T=class{baseData;constructor({baseData:e}){this.baseData=e}generateMetadata(e){let{icons:t,metadataBase:n,appLinks:r,keywords:i,twitter:a,openGraph:o}=this.baseData,s=[{url:`https://coldsurf.io/icons/favicon.ico`}],c={...e,icons:t,metadataBase:n,appLinks:r,keywords:[...e.keywords??[],...i],twitter:a,openGraph:{siteName:o?.siteName,images:Array.isArray(o?.images)&&o.images.length>0?o.images:s,title:o?.title,description:o?.description,...e.openGraph},locale:`ko`};return c}generateLdJson(e){switch(e.type){case`WebSite`:return{"@context":`https://schema.org`,"@type":`WebSite`,url:e.url,name:e.name};case`MusicEvent`:return w(e);case`Brand`:return{"@context":`https://schema.org`,"@type":`Brand`,name:e.name,image:e.image,logo:e.logo,url:e.url,sameAs:e.sameAs};case`Place`:return{"@context":`https://schema.org`,"@type":`Place`,address:e.address,event:e.events.map(e=>w(e)),geo:{"@type":`GeoCoordinates`,latitude:e.latitude,longitude:e.longitude},name:e.name,url:e.url,description:e.description};case`PerformingGroup`:return{"@context":`https://schema.org`,"@type":`PerformingGroup`,image:e.image,name:e.name,url:e.url,event:e.events.map(e=>w(e))};case`WebPageAbout`:return{"@context":`https://schema.org`,name:e.name,"@type":`AboutPage`,url:e.url,image:e.image,sameAs:e.sameAs,description:e.description};default:return{}}}};function E(e,t){let n=Math.ceil(e),r=Math.floor(t);return Math.floor(Math.random()*(r-n+1))+n}function D(){let e=new Date().getTime(),t=typeof performance<`u`&&performance.now&&performance.now()*1e3||0;return`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,n=>{let r=Math.random()*16;return e>0?(r=(e+r)%16|0,e=Math.floor(e/16)):(r=(t+r)%16|0,t=Math.floor(t/16)),(n===`x`?r:r&3|8).toString(16)})}function O(e,{silent:t=!0,fallback:n}={}){try{return JSON.parse(e)}catch(r){return t||console.warn(`JSON parse error, return fallback:`,r,`| input:`,e),n}}const k=e=>c(e,{replacement:`-`,lower:!0,strict:!1,remove:/[[\]*+~.()'"?!:@,&<>〈〉#]/g});async function A(e,t){let n=F(e),r=await t(n);if(r){let e=1,i;do i=`${n}-${e}`,r=await t(i),e++;while(r);n=i}return n}const j=[[/#/g,`no`],[/&/g,`and`],[/%/g,`percent`]];function M(e){return j.reduce((e,[t,n])=>e.replace(t,n),e)}function N(e){if(e>3&&e<21)return`th`;switch(e%10){case 1:return`st`;case 2:return`nd`;case 3:return`rd`;default:return`th`}}function P(e){let t=Number(r(e,`d`)),n=r(e,`MMM`).toLowerCase(),i=N(t);return`${t}${i}-${n}`}const F=e=>{let t=c(M(`${e}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},I=e=>{let t=c(M(`${e}`),{replacement:`_`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},L=({title:e,date:t,venueName:n,area:r})=>{let i=`${e}-${P(t)}`;n&&(i+=`-${n}`),r&&(i+=`-${r}`),i+=`-티켓`;let a=c(M(`${i}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return a};async function R({title:e,date:t,venueName:n,area:r},i){let a=L({title:e,date:t,venueName:n,area:r}),o=await i(a);if(o){let e=1,t;do t=`${a}-${e}`,o=await i(t),e++;while(o);a=t}return a}const z=e=>{switch(e){case`Gigs`:return`콘서트`;case`Theatre`:return`연극 / 뮤지컬`;case`Dance`:return`무용`;case`Korean-Traditional`:return`국악`;case`Classic`:return`클래식`;case`Party`:return`파티 / 오프라인`;case`Dj`:return`디제잉`;default:return e}},B={getEventCategoryUIName:z},V=e=>{switch(e.toLowerCase()){case`seoul`:return`서울`;case`incheon`:return`인천`;case`yeongjongdo`:return`영종도`;case`ulsan`:return`울산`;case`busan`:return`부산`;case`daegu`:return`대구`;case`jeju`:return`제주`;case`gyeongsangbuk-do`:return`경상북도`;case`gyeongsangnam-do`:return`경상남도`;case`gwangju`:return`광주`;case`daejeon`:return`대전`;case`sejong-city`:return`세종시`;case`gyeonggi-do`:return`경기도`;case`gangwon-do`:return`강원도`;case`chungcheongbuk-do`:return`충청북도`;case`chungcheongnam-do`:return`충청남도`;case`jeollabuk-do`:return`전라북도`;case`jeollanam-do`:return`전라남도`;case`tokyo`:return`도쿄`;case`osaka`:return`오사카`;case`hochiminh`:return`호치민`;default:return e}},H={getLocationCityUIName:V},U=`Asia/Seoul`;function W(e,t=new Date){return!a(e,t)}function G(e,t){let n=u(e,U),i=n.getMinutes();if(t?.formatStyle===`english`){let e=(()=>W(n)?`MMM dd, yyyy, h:mm a`:`MMM dd, h:mm a`)();return r(n,e,{locale:d})}let a=(()=>W(n)?i===0?`EEEE a h시, yyyy년 MMMM d일`:`EEEE a h시 m분, yyyy년 MMMM d일`:i===0?`EEEE a h시, MMMM d일`:`EEEE a h시 m분, MMMM d일`)();return r(n,a,{locale:f})}function K({yyyymmdd:e}){if(!/^\d{8}$/.test(e))throw Error(`Invalid date format. Expected YYYYMMDD`);let t=o(e,`yyyyMMdd`,new Date),r=n(t,1),i=l(t,U),a=l(r,U);return[i,a]}function q(e){let t=e?.yyyymmdd?o(e.yyyymmdd,`yyyyMMdd`,new Date):new Date,r=i(t),a=(5-r+7)%7,c=s(n(t,a)),u=n(c,1),d=n(c,2),f=n(c,3);return[{label:`FRIDAY`,utcStart:l(c,U),utcEnd:l(u,U)},{label:`SATURDAY`,utcStart:l(u,U),utcEnd:l(d,U)},{label:`SUNDAY`,utcStart:l(d,U),utcEnd:l(f,U)}]}const J={parseEventDate:G,toUTCDayRangeFromYYYYMMDD:K,getWeekendUTCStartDates:q};function Y(e){let t=e,n=``;for(;t!==n;){n=t;try{t=decodeURIComponent(t)}catch{break}}return t}function X(e){try{let t=new URL(e);return t.pathname=Y(t.pathname),t.toString()}catch{return Y(e)}}function Z(e){return e.includes(`%`)}function Q(e){return/%25(25)+/i.test(e)}export{p as APP_STORE_ID,m as APP_STORE_URL,b as COLDSURF_WEB_URL,T as NextMetadataGenerator,v as PLAYSTORE_APP_NAME,g as PLAYSTORE_PACKAGE,_ as PLAYSTORE_URL,h as SERVICE_NAME,y as SNS_LINKS,L as createConcertSlug,F as createSlug,I as createSlugHashtag,J as dateUtils,C as decodeJwt,B as eventCategoryUtils,X as fullyDecodePathname,Y as fullyDecodeURI,R as generateConcertSlug,A as generateSlug,D as generateUUID,E as getRandomInt,k as getSafeSlug,Q as isDoubleEncoded,Z as isEncoded,H as locationCityUtils,x as loginProviderSchema,S as pickFile,O as tryParse};
import{z as e}from"zod";import{jwtDecode as t}from"jwt-decode";import{addDays as n,format as r,getDay as i,isSameYear as a,parse as o,startOfDay as s}from"date-fns";import c from"slugify";import{fromZonedTime as l,toZonedTime as u}from"date-fns-tz";import{enUS as d,ko as f}from"date-fns/locale";const p=`1632802589`,m=`https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${p}`,h=`COLDSURF`,ee=`com.fstvllife.android`,te=`https://play.google.com/store/apps/details?id=com.fstvllife.android`,ne=`COLDSURF`,g={INSTAGRAM:`https://www.instagram.com/coldsurf.io`,X:`https://x.com/coldsurf_io`},_=`https://coldsurf.io`,re={ko:`ko_KR`,en:`en_US`},v=`https://schema.org`;function y(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function b(e,t){let n=e.replace(/\/$/,``),r=t.startsWith(`/`)?t:`/${t}`;return`${n}${r}`}function x(e,t){return t.startsWith(`http`)?t:b(e,t)}function ie(e,t,n){return!t||n?e:`${e}${e.includes(`?`)?`&`:`?`}v=${encodeURIComponent(t)}`}function S(e,t){return x(e,t)}function C(e,t){let n=e.publisher;return n?{"@type":`Organization`,"@id":t,name:n.name,url:n.url??e.baseUrl.replace(/\/$/,``),logo:n.logoPath?b(e.baseUrl,n.logoPath):void 0,sameAs:n.sameAs}:null}function w(e,t){let n=e.editor;return n?{"@type":`Person`,"@id":t,name:n.name,url:n.url,sameAs:n.sameAs}:null}function T(e,t,n,r){return{"@type":`WebSite`,"@id":n,name:e.name,url:e.baseUrl.replace(/\/$/,``),inLanguage:t.lang,publisher:e.publisher?{"@id":r}:void 0}}function E(e,t,n,r,i){let a=e.article;return a?{"@type":`BlogPosting`,headline:e.title,description:e.description,url:n.url,mainEntityOfPage:n.url,inLanguage:n.lang,datePublished:a.publishedTime,dateModified:a.modifiedTime,image:n.imageUrl,articleSection:a.section,keywords:a.tags,author:t.editor?{"@id":i}:a.author?{"@type":`Person`,name:a.author}:void 0,publisher:t.publisher?{"@id":r}:void 0}:null}function D(e,t){let n=e.name,r=e.image?x(t,e.image):void 0,i=e.url?S(t,e.url):void 0,a=e.sameAs?.length?e.sameAs:void 0,o=e.year==null?void 0:String(e.year),s=e.by?{"@type":`MusicGroup`,name:e.by}:void 0,c=e.by?{"@type":`Person`,name:e.by}:void 0;switch(e.kind){case`albums`:return{"@type":`MusicAlbum`,name:n,byArtist:s,image:r,url:i,sameAs:a,datePublished:o};case`tracks`:return{"@type":`MusicRecording`,name:n,byArtist:s,image:r,url:i,sameAs:a};case`concerts`:return{"@type":`MusicEvent`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`events`:return{"@type":`Event`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`films`:return{"@type":`Movie`,name:n,director:c,image:r,url:i,sameAs:a,datePublished:o};case`books`:return{"@type":`Book`,name:n,author:c,image:r,url:i,sameAs:a,datePublished:o}}}function O(e,t,n,r,i,a,o,s){let c=n.article;return{"@type":`Review`,name:i.fullTitle,reviewBody:n.description,url:i.url,inLanguage:i.lang,datePublished:c?.publishedTime,author:r.editor?{"@id":s}:c?.author?{"@type":`Person`,name:c.author}:void 0,publisher:r.publisher?{"@id":o}:void 0,itemReviewed:D(e,a),reviewRating:t?{"@type":`Rating`,ratingValue:t.value,bestRating:t.best,worstRating:t.worst}:void 0}}function k(e,t){return{"@type":`BreadcrumbList`,itemListElement:e.map((e,n)=>({"@type":`ListItem`,position:n+1,name:e.name,item:S(t,e.path)}))}}function A(e,t){return{"@type":`MusicEvent`,name:e.name,url:S(t,e.url),startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}},image:e.images?.length?e.images.map(e=>x(t,e)):void 0,description:e.description,offers:e.offers?.length?e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:x(t,e.url),validFrom:e.validFrom,name:e.name})):void 0,organizer:{"@type":`Organization`,name:e.organizer??e.venue.name}}}function j(e,t,n){let r=e.jsonLd??[];if(r.length===0)return[];let i=t.baseUrl.replace(/\/$/,``),a=`${i}/#org`,o=`${i}/#editor`,s=`${i}/#website`,c=[];for(let l of r)switch(l.type){case`WebSite`:c.push(T(t,n,s,a));break;case`Article`:{let r=E(e,t,n,a,o);r&&c.push(r);break}case`Review`:c.push(O(l.itemReviewed,l.rating,e,t,n,i,a,o));break;case`CreativeWork`:c.push(D(l.item,i));break;case`EventPage`:c.push(A(l.event,i));break;case`Breadcrumb`:c.push(k(l.items,i));break}if(c.length===0)return[];let l=[],u=C(t,a);u&&l.push(u);let d=w(t,o);d&&l.push(d);let f={"@context":v,"@graph":[...l,...c]};return[{type:`application/ld+json`,innerHTML:JSON.stringify(f)}]}function M(e,t){let n=e.lang??`ko`,r=RegExp(`\\b${y(t.name)}\\b`).test(e.title),i=r?e.title:`${e.title} | ${t.name}`,a=S(t.baseUrl,e.path),o=e.alternates??[],s={...re,...t.locales},c=[{rel:`canonical`,href:a}];if(o.length>0){c.push({rel:`alternate`,hreflang:n,href:a});for(let e of o)c.push({rel:`alternate`,hreflang:e.lang,href:S(t.baseUrl,e.path)});let r=o.find(e=>e.lang===`ko`)?.path??e.path;c.push({rel:`alternate`,hreflang:`x-default`,href:S(t.baseUrl,r)})}let l=[{name:`description`,content:e.description},{name:`robots`,content:`index, follow`},{property:`og:title`,content:i},{property:`og:description`,content:e.description},{property:`og:type`,content:e.article?`article`:`website`},{property:`og:url`,content:a},{property:`og:site_name`,content:t.name},{property:`og:locale`,content:s[n]}],u=[...new Set([...e.keywords??[],...t.keywords??[]])];u.length>0&&l.push({name:`keywords`,content:u.join(`, `)});let d=e.image??t.defaultImage,f=d?ie(x(t.baseUrl,d.path),t.assetVersion,d.path.startsWith(`http`)):void 0;d&&f&&(l.push({property:`og:image`,content:f}),d.type&&l.push({property:`og:image:type`,content:d.type}),d.width&&l.push({property:`og:image:width`,content:String(d.width)}),d.height&&l.push({property:`og:image:height`,content:String(d.height)}),d.alt&&l.push({property:`og:image:alt`,content:d.alt}),l.push({name:`twitter:image`,content:f}),d.alt&&l.push({name:`twitter:image:alt`,content:d.alt})),t.logoPath&&l.push({property:`og:logo`,content:b(t.baseUrl,t.logoPath)});let p=!!d&&!!d.width&&!!d.height&&d.width>=d.height*1.5,m=p?`summary_large_image`:`summary`;l.push({name:`twitter:card`,content:m},{name:`twitter:title`,content:i},{name:`twitter:description`,content:e.description});for(let e of o)e.lang!==n&&l.push({property:`og:locale:alternate`,content:s[e.lang]});if(e.article){l.push({property:`article:published_time`,content:e.article.publishedTime}),e.article.modifiedTime&&l.push({property:`article:modified_time`,content:e.article.modifiedTime}),e.article.author&&l.push({property:`article:author`,content:e.article.author}),e.article.section&&l.push({property:`article:section`,content:e.article.section});for(let t of e.article.tags??[])l.push({property:`article:tag`,content:t})}t.appLinks?.ios&&(l.push({property:`al:ios:app_store_id`,content:t.appLinks.ios.appStoreId}),l.push({property:`al:ios:app_name`,content:t.appLinks.ios.appName}),e.appLinks?.iosUrl&&l.push({property:`al:ios:url`,content:e.appLinks.iosUrl})),t.appLinks?.android&&(l.push({property:`al:android:package`,content:t.appLinks.android.packageName}),t.appLinks.android.appName&&l.push({property:`al:android:app_name`,content:t.appLinks.android.appName}),e.appLinks?.androidUrl&&l.push({property:`al:android:url`,content:e.appLinks.androidUrl}));let h=j(e,t,{url:a,fullTitle:i,imageUrl:f,lang:n});return{title:i,htmlLang:n,meta:l,link:c,script:h}}function N(e){return{buildTags:t=>M(t,e)}}const P=e.union([e.literal(`google`),e.literal(`apple`),e.literal(`email`)]);function F(e){let t=document.createElement(`input`);t.type=`file`,t.click(),t.onchange=async n=>{await e(n),t.remove()}}const I=e=>{try{let n=t(e);return n}catch(e){return console.error(`Error decoding JWT:`,e),null}};function L(e,t){let n=Math.ceil(e),r=Math.floor(t);return Math.floor(Math.random()*(r-n+1))+n}function R(){let e=new Date().getTime(),t=typeof performance<`u`&&performance.now&&performance.now()*1e3||0;return`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`.replace(/[xy]/g,n=>{let r=Math.random()*16;return e>0?(r=(e+r)%16|0,e=Math.floor(e/16)):(r=(t+r)%16|0,t=Math.floor(t/16)),(n===`x`?r:r&3|8).toString(16)})}function z(e,{silent:t=!0,fallback:n}={}){try{return JSON.parse(e)}catch(r){return t||console.warn(`JSON parse error, return fallback:`,r,`| input:`,e),n}}const B=e=>c(e,{replacement:`-`,lower:!0,strict:!1,remove:/[[\]*+~.()'"?!:@,&<>〈〉#]/g});async function V(e,t){let n=K(e),r=await t(n);if(r){let e=1,i;do i=`${n}-${e}`,r=await t(i),e++;while(r);n=i}return n}const H=[[/#/g,`no`],[/&/g,`and`],[/%/g,`percent`]];function U(e){return H.reduce((e,[t,n])=>e.replace(t,n),e)}function W(e){if(e>3&&e<21)return`th`;switch(e%10){case 1:return`st`;case 2:return`nd`;case 3:return`rd`;default:return`th`}}function G(e){let t=Number(r(e,`d`)),n=r(e,`MMM`).toLowerCase(),i=W(t);return`${t}${i}-${n}`}const K=e=>{let t=c(U(`${e}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},q=e=>{let t=c(U(`${e}`),{replacement:`_`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return t},J=({title:e,date:t,venueName:n,area:r})=>{let i=`${e}-${G(t)}`;n&&(i+=`-${n}`),r&&(i+=`-${r}`),i+=`-티켓`;let a=c(U(`${i}`),{replacement:`-`,lower:!0,strict:!1,remove:/[\/[\]*+~.()'"?!:@,<>〈〉]/g});return a};async function Y({title:e,date:t,venueName:n,area:r},i){let a=J({title:e,date:t,venueName:n,area:r}),o=await i(a);if(o){let e=1,t;do t=`${a}-${e}`,o=await i(t),e++;while(o);a=t}return a}const ae=e=>{switch(e){case`Gigs`:return`콘서트`;case`Theatre`:return`연극 / 뮤지컬`;case`Dance`:return`무용`;case`Korean-Traditional`:return`국악`;case`Classic`:return`클래식`;case`Party`:return`파티 / 오프라인`;case`Dj`:return`디제잉`;default:return e}},oe={getEventCategoryUIName:ae},se=e=>{switch(e.toLowerCase()){case`seoul`:return`서울`;case`incheon`:return`인천`;case`yeongjongdo`:return`영종도`;case`ulsan`:return`울산`;case`busan`:return`부산`;case`daegu`:return`대구`;case`jeju`:return`제주`;case`gyeongsangbuk-do`:return`경상북도`;case`gyeongsangnam-do`:return`경상남도`;case`gwangju`:return`광주`;case`daejeon`:return`대전`;case`sejong-city`:return`세종시`;case`gyeonggi-do`:return`경기도`;case`gangwon-do`:return`강원도`;case`chungcheongbuk-do`:return`충청북도`;case`chungcheongnam-do`:return`충청남도`;case`jeollabuk-do`:return`전라북도`;case`jeollanam-do`:return`전라남도`;case`tokyo`:return`도쿄`;case`osaka`:return`오사카`;case`hochiminh`:return`호치민`;default:return e}},ce={getLocationCityUIName:se},X=`Asia/Seoul`;function Z(e,t=new Date){return!a(e,t)}function le(e,t){let n=u(e,X),i=n.getMinutes();if(t?.formatStyle===`english`){let e=(()=>Z(n)?`MMM dd, yyyy, h:mm a`:`MMM dd, h:mm a`)();return r(n,e,{locale:d})}let a=(()=>Z(n)?i===0?`EEEE a h시, yyyy년 MMMM d일`:`EEEE a h시 m분, yyyy년 MMMM d일`:i===0?`EEEE a h시, MMMM d일`:`EEEE a h시 m분, MMMM d일`)();return r(n,a,{locale:f})}function ue({yyyymmdd:e}){if(!/^\d{8}$/.test(e))throw Error(`Invalid date format. Expected YYYYMMDD`);let t=o(e,`yyyyMMdd`,new Date),r=n(t,1),i=l(t,X),a=l(r,X);return[i,a]}function Q(e){let t=e?.yyyymmdd?o(e.yyyymmdd,`yyyyMMdd`,new Date):new Date,r=i(t),a=(5-r+7)%7,c=s(n(t,a)),u=n(c,1),d=n(c,2),f=n(c,3);return[{label:`FRIDAY`,utcStart:l(c,X),utcEnd:l(u,X)},{label:`SATURDAY`,utcStart:l(u,X),utcEnd:l(d,X)},{label:`SUNDAY`,utcStart:l(d,X),utcEnd:l(f,X)}]}const de={parseEventDate:le,toUTCDayRangeFromYYYYMMDD:ue,getWeekendUTCStartDates:Q};function $(e){let t=e,n=``;for(;t!==n;){n=t;try{t=decodeURIComponent(t)}catch{break}}return t}function fe(e){try{let t=new URL(e);return t.pathname=$(t.pathname),t.toString()}catch{return $(e)}}function pe(e){return e.includes(`%`)}function me(e){return/%25(25)+/i.test(e)}export{p as APP_STORE_ID,m as APP_STORE_URL,_ as COLDSURF_WEB_URL,ne as PLAYSTORE_APP_NAME,ee as PLAYSTORE_PACKAGE,te as PLAYSTORE_URL,h as SERVICE_NAME,g as SNS_LINKS,M as buildSeoTags,J as createConcertSlug,N as createSeo,K as createSlug,q as createSlugHashtag,de as dateUtils,I as decodeJwt,oe as eventCategoryUtils,fe as fullyDecodePathname,$ as fullyDecodeURI,Y as generateConcertSlug,V as generateSlug,R as generateUUID,L as getRandomInt,B as getSafeSlug,me as isDoubleEncoded,pe as isEncoded,ce as locationCityUtils,P as loginProviderSchema,F as pickFile,z as tryParse};
//# sourceMappingURL=index.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","names":["onChange: (e: Event) => void | Promise<void>","token: string","event: CustomMusicEvent","additionalMetadata: T","params:\n | {\n type: 'WebSite';\n url: string;\n name: string;\n }\n | CustomMusicEvent\n | {\n type: 'Brand';\n name: string;\n image: string;\n logo: string;\n url: string;\n sameAs: string[];\n }\n | {\n type: 'Place';\n address: string;\n latitude: number;\n longitude: number;\n name: string;\n url: string;\n events: CustomMusicEvent[];\n description: string;\n }\n | {\n type: 'PerformingGroup';\n image: string;\n name: string;\n url: string;\n events: CustomMusicEvent[];\n }\n | {\n type: 'WebPageAbout';\n name: string;\n url: string;\n image: string;\n sameAs: string[];\n description: string;\n }","minimum: number","maximum: number","jsonString: string","slug: string","title: string","existingCallback: (newSlug: string) => boolean | Promise<boolean>","newSlug: string","day: number","date: Date","valueToSlugify: string","originalName: string","originalName: string","date: Date","now: Date","eventDate: Date","options?: {\n formatStyle: 'korean' | 'english';\n }","timeFormat","params?: {\n yyyymmdd?: string;\n}","uri: string","url: string","str: string"],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.metadata.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":["export const APP_STORE_ID = '1632802589' as const;\nexport const APP_STORE_URL =\n `https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${APP_STORE_ID}` as const;\n","export const SERVICE_NAME = 'COLDSURF';\n","export const PLAYSTORE_PACKAGE = 'com.fstvllife.android' as const;\nexport const PLAYSTORE_URL =\n 'https://play.google.com/store/apps/details?id=com.fstvllife.android' as const;\nexport const PLAYSTORE_APP_NAME = 'COLDSURF' as const;\n","export const SNS_LINKS = {\n INSTAGRAM: 'https://www.instagram.com/coldsurf.io',\n X: 'https://x.com/coldsurf_io',\n} as const;\n","export const COLDSURF_WEB_URL = 'https://coldsurf.io';\n","import { z } from 'zod';\n\nexport const loginProviderSchema = z.union([\n z.literal('google'),\n z.literal('apple'),\n z.literal('email'),\n]);\nexport type LoginProvider = z.infer<typeof loginProviderSchema>;\n","export function pickFile(onChange: (e: Event) => void | Promise<void>) {\n const input = document.createElement('input');\n input.type = 'file';\n input.click();\n input.onchange = async (e) => {\n await onChange(e);\n input.remove();\n };\n}\n","// Import the jwt-decode library\nimport { jwtDecode } from 'jwt-decode';\n\n// Define the structure of the decoded JWT payload (optional)\ninterface JwtPayload {\n sub: string; // Subject (user identifier)\n name: string; // Name of the user\n email: string; // Email of the user\n exp: number; // Expiration time\n [key: string]: unknown; // Any other properties\n}\n\n// Function to parse JWT token\nexport const decodeJwt = (token: string): JwtPayload | null => {\n try {\n // Decode the token\n const decodedToken = jwtDecode<JwtPayload>(token);\n return decodedToken;\n } catch (error) {\n console.error('Error decoding JWT:', error);\n return null;\n }\n};\n","import type {\n Brand,\n MusicEvent,\n PerformingGroup,\n Place,\n WebPage,\n WebSite,\n WithContext,\n} from 'schema-dts';\n\ntype BaseData = {\n keywords: string[];\n icons: {\n icon: string;\n shortcut: string;\n apple: string;\n };\n metadataBase: URL;\n appLinks?: {\n ios?: {\n app_name: string;\n app_store_id: string;\n url: string;\n };\n android?: {\n package: string;\n url?: string | URL;\n class?: string;\n app_name?: string;\n };\n };\n twitter?: {\n app?: {\n id: {\n /**\n * app store id\n */\n iphone: string;\n };\n name: string;\n url: {\n /**\n * app store url\n */\n iphone: string;\n };\n };\n card: 'app';\n };\n openGraph?: {\n siteName: string;\n title: string;\n description: string;\n images?: {\n url: string;\n }[];\n };\n};\n\ntype CustomMusicEvent = {\n type: 'MusicEvent';\n url: string;\n name: string;\n startDate: string;\n endDate: string;\n venue: {\n name: string;\n address: string;\n latitude: number;\n longitude: number;\n };\n images: string[];\n description: string;\n offers: {\n price: number;\n currency: string;\n url: string;\n validFrom: string;\n name?: string;\n }[];\n};\n\nfunction generateMusicEvent(event: CustomMusicEvent) {\n return {\n '@context': 'https://schema.org',\n '@type': 'MusicEvent',\n url: event.url,\n name: event.name,\n startDate: event.startDate,\n endDate: event.endDate,\n eventAttendanceMode: 'https://schema.org/OfflineEventAttendanceMode',\n eventStatus: 'https://schema.org/EventScheduled',\n location: [\n {\n '@type': 'Place',\n name: event.venue.name,\n address: event.venue.address,\n geo: {\n '@type': 'GeoCoordinates',\n latitude: event.venue.latitude,\n longitude: event.venue.longitude,\n },\n },\n ],\n image: event.images,\n description: event.description,\n offers: event.offers.map((offer) => {\n return {\n '@type': 'Offer',\n availability: 'https://schema.org/InStock',\n price: offer.price,\n priceCurrency: offer.currency,\n url: offer.url,\n validFrom: offer.validFrom,\n name: offer.name,\n };\n }),\n organizer: {\n '@type': 'Organization',\n name: event.venue.name,\n },\n } satisfies WithContext<MusicEvent>;\n}\n\nexport class NextMetadataGenerator {\n public baseData: BaseData;\n constructor({ baseData }: { baseData: BaseData }) {\n this.baseData = baseData;\n }\n\n public generateMetadata<T>(additionalMetadata: T): T {\n const { icons, metadataBase, appLinks, keywords, twitter, openGraph } =\n this.baseData;\n const defaultOgImages = [\n {\n url: 'https://coldsurf.io/icons/favicon.ico',\n },\n ];\n const value = {\n ...additionalMetadata,\n icons,\n metadataBase,\n appLinks,\n // @ts-ignore\n keywords: [...(additionalMetadata.keywords ?? []), ...keywords],\n twitter,\n openGraph: {\n siteName: openGraph?.siteName,\n images:\n Array.isArray(openGraph?.images) && openGraph.images.length > 0\n ? openGraph.images\n : defaultOgImages,\n title: openGraph?.title,\n description: openGraph?.description,\n // @ts-ignore\n ...additionalMetadata.openGraph,\n },\n locale: 'ko',\n } as T;\n\n return value;\n }\n\n public generateLdJson(\n params:\n | {\n type: 'WebSite';\n url: string;\n name: string;\n }\n | CustomMusicEvent\n | {\n type: 'Brand';\n name: string;\n image: string;\n logo: string;\n url: string;\n sameAs: string[];\n }\n | {\n type: 'Place';\n address: string;\n latitude: number;\n longitude: number;\n name: string;\n url: string;\n events: CustomMusicEvent[];\n description: string;\n }\n | {\n type: 'PerformingGroup';\n image: string;\n name: string;\n url: string;\n events: CustomMusicEvent[];\n }\n | {\n type: 'WebPageAbout';\n name: string;\n url: string;\n image: string;\n sameAs: string[];\n description: string;\n }\n ) {\n switch (params.type) {\n case 'WebSite':\n return {\n '@context': 'https://schema.org',\n '@type': 'WebSite',\n url: params.url,\n name: params.name,\n } satisfies WithContext<WebSite>;\n case 'MusicEvent':\n return generateMusicEvent(params);\n case 'Brand':\n return {\n '@context': 'https://schema.org',\n '@type': 'Brand',\n name: params.name,\n image: params.image,\n logo: params.logo,\n url: params.url,\n sameAs: params.sameAs,\n } satisfies WithContext<Brand>;\n case 'Place':\n return {\n '@context': 'https://schema.org',\n '@type': 'Place',\n address: params.address,\n event: params.events.map((event) => generateMusicEvent(event)),\n geo: {\n '@type': 'GeoCoordinates',\n latitude: params.latitude,\n longitude: params.longitude,\n },\n name: params.name,\n url: params.url,\n description: params.description,\n } satisfies WithContext<Place>;\n case 'PerformingGroup':\n return {\n '@context': 'https://schema.org',\n '@type': 'PerformingGroup',\n image: params.image,\n name: params.name,\n url: params.url,\n event: params.events.map((event) => generateMusicEvent(event)),\n } satisfies WithContext<PerformingGroup>;\n case 'WebPageAbout':\n return {\n '@context': 'https://schema.org',\n name: params.name,\n '@type': 'AboutPage',\n url: params.url,\n image: params.image,\n sameAs: params.sameAs,\n description: params.description,\n } satisfies WithContext<WebPage>;\n default:\n return {};\n }\n }\n}\n","/**\n * Returns a random integer between the specified values, inclusive.\n * The value is no lower than `min`, and is less than or equal to `max`.\n *\n * @param {number} minimum - The smallest integer value that can be returned, inclusive.\n * @param {number} maximum - The largest integer value that can be returned, inclusive.\n * @returns {number} - A random integer between `min` and `max`, inclusive.\n */\nexport function getRandomInt(minimum: number, maximum: number) {\n const min = Math.ceil(minimum);\n const max = Math.floor(maximum);\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n","export function generateUUID() {\n // Public Domain/MIT\n let d = new Date().getTime(); // Timestamp\n let d2 =\n (typeof performance !== 'undefined' &&\n performance.now &&\n performance.now() * 1000) ||\n 0; // Time in microseconds since page-load or 0 if unsupported\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n let r = Math.random() * 16; // random number between 0 and 16\n if (d > 0) {\n // Use timestamp until depleted\n r = ((d + r) % 16) | 0;\n d = Math.floor(d / 16);\n } else {\n // Use microseconds since page-load if supported\n r = ((d2 + r) % 16) | 0;\n d2 = Math.floor(d2 / 16);\n }\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n","export interface TryParseOptions<T> {\n fallback?: T;\n silent?: boolean;\n}\n\nexport function tryParse<T = any>(\n jsonString: string,\n { silent = true, fallback }: TryParseOptions<T> = {}\n): T | undefined {\n try {\n return JSON.parse(jsonString) as T;\n } catch (e) {\n if (!silent) {\n console.warn(\n 'JSON parse error, return fallback:',\n e,\n '| input:',\n jsonString\n );\n }\n\n return fallback;\n }\n}\n","import { format } from 'date-fns';\nimport slugify from 'slugify';\n\nexport const getSafeSlug = (slug: string) => {\n return slugify(slug, {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[[\\]*+~.()'\"?!:@,&<>〈〉#]/g, // 특정 특수문자 제거\n });\n};\n\n// Function to generate unique slugs\nexport async function generateSlug(\n title: string,\n existingCallback: (newSlug: string) => boolean | Promise<boolean>\n) {\n let slug = createSlug(title);\n\n // Check for existing slugs in the database\n let existing = await existingCallback(slug);\n\n // // If slug already exists, append a number\n if (existing) {\n let counter = 1;\n let newSlug: string;\n do {\n newSlug = `${slug}-${counter}`;\n existing = await existingCallback(newSlug);\n counter++;\n } while (existing);\n slug = newSlug;\n }\n\n return slug;\n}\n\nconst replacements = [\n [/#/g, 'no'],\n [/&/g, 'and'],\n [/%/g, 'percent'],\n] as const;\n\nfunction preprocess(title: string) {\n return replacements.reduce(\n (acc, [regex, value]) => acc.replace(regex, value),\n title\n );\n}\n\n// 서수 접미사 함수\nfunction getOrdinalSuffix(day: number): string {\n if (day > 3 && day < 21) return 'th';\n switch (day % 10) {\n case 1:\n return 'st';\n case 2:\n return 'nd';\n case 3:\n return 'rd';\n default:\n return 'th';\n }\n}\n\nfunction formatDateSlug(date: Date): string {\n const day = Number(format(date, 'd')); // 1~31\n const month = format(date, 'MMM').toLowerCase(); // \"Oct\" → \"oct\"\n const ordinal = getOrdinalSuffix(day);\n return `${day}${ordinal}-${month}`;\n}\n\nexport const createSlug = (valueToSlugify: string) => {\n const slug = slugify(preprocess(`${valueToSlugify}`), {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n return slug;\n};\n\nexport const createSlugHashtag = (valueToSlugify: string) => {\n const slug = slugify(preprocess(`${valueToSlugify}`), {\n replacement: '_', // 공백을 \"_\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n return slug;\n};\n\n// Function to generate unique slugs\nexport const createConcertSlug = ({\n title,\n date,\n venueName,\n area,\n}: {\n title: string;\n date: Date;\n venueName?: string;\n area?: string;\n}) => {\n let value = `${title}-${formatDateSlug(date)}`;\n if (venueName) {\n value += `-${venueName}`;\n }\n if (area) {\n value += `-${area}`;\n }\n value += '-티켓';\n const slug = slugify(preprocess(`${value}`), {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n\n return slug;\n};\n\n// Function to generate unique slugs\nexport async function generateConcertSlug(\n { title, date, venueName, area }: Parameters<typeof createConcertSlug>[0],\n existingCallback: (newSlug: string) => boolean | Promise<boolean>\n) {\n let slug = createConcertSlug({ title, date, venueName, area });\n\n // Check for existing slugs in the database\n let existing = await existingCallback(slug);\n\n // // If slug already exists, append a number\n if (existing) {\n let counter = 1;\n let newSlug: string;\n do {\n newSlug = `${slug}-${counter}`;\n existing = await existingCallback(newSlug);\n counter++;\n } while (existing);\n slug = newSlug;\n }\n\n return slug;\n}\n","const getEventCategoryUIName = (originalName: string) => {\n switch (originalName) {\n case 'Gigs':\n return '콘서트';\n case 'Theatre':\n return '연극 / 뮤지컬';\n case 'Dance':\n return '무용';\n case 'Korean-Traditional':\n return '국악';\n case 'Classic':\n return '클래식';\n case 'Party':\n return '파티 / 오프라인';\n case 'Dj':\n return '디제잉';\n default:\n return originalName;\n }\n};\n\nexport const eventCategoryUtils = {\n getEventCategoryUIName,\n};\n","const getLocationCityUIName = (originalName: string) => {\n switch (originalName.toLowerCase()) {\n case 'seoul':\n return '서울';\n case 'incheon':\n return '인천';\n case 'yeongjongdo':\n return '영종도';\n case 'ulsan':\n return '울산';\n case 'busan':\n return '부산';\n case 'daegu':\n return '대구';\n case 'jeju':\n return '제주';\n case 'gyeongsangbuk-do':\n return '경상북도';\n case 'gyeongsangnam-do':\n return '경상남도';\n case 'gwangju':\n return '광주';\n case 'daejeon':\n return '대전';\n case 'sejong-city':\n return '세종시';\n case 'gyeonggi-do':\n return '경기도';\n case 'gangwon-do':\n return '강원도';\n case 'chungcheongbuk-do':\n return '충청북도';\n case 'chungcheongnam-do':\n return '충청남도';\n case 'jeollabuk-do':\n return '전라북도';\n case 'jeollanam-do':\n return '전라남도';\n case 'tokyo':\n return '도쿄';\n case 'osaka':\n return '오사카';\n case 'hochiminh':\n return '호치민';\n default:\n return originalName;\n }\n};\n\nexport const locationCityUtils = {\n getLocationCityUIName,\n};\n","import { format, isSameYear, addDays, getDay, parse, startOfDay } from 'date-fns';\nimport { fromZonedTime } from 'date-fns-tz';\nimport { toZonedTime } from 'date-fns-tz';\nimport { enUS, ko } from 'date-fns/locale';\n\nconst timeZone = 'Asia/Seoul';\n\nfunction isDifferentYear(date: Date, now: Date = new Date()) {\n return !isSameYear(date, now);\n}\n\nfunction parseEventDate(\n eventDate: Date,\n options?: {\n formatStyle: 'korean' | 'english';\n }\n) {\n const zonedDate = toZonedTime(eventDate, timeZone);\n const minutes = zonedDate.getMinutes();\n if (options?.formatStyle === 'english') {\n const timeFormat = (() => {\n if (isDifferentYear(zonedDate)) {\n return 'MMM dd, yyyy, h:mm a';\n }\n return 'MMM dd, h:mm a';\n })();\n return format(zonedDate, timeFormat, { locale: enUS });\n }\n const timeFormat = (() => {\n if (isDifferentYear(zonedDate)) {\n return minutes === 0\n ? 'EEEE a h시, yyyy년 MMMM d일'\n : 'EEEE a h시 m분, yyyy년 MMMM d일';\n }\n return minutes === 0 ? 'EEEE a h시, MMMM d일' : 'EEEE a h시 m분, MMMM d일';\n })();\n return format(zonedDate, timeFormat, { locale: ko });\n}\n\nfunction toUTCDayRangeFromYYYYMMDD({\n yyyymmdd,\n}: {\n yyyymmdd: string;\n}) {\n if (!/^\\d{8}$/.test(yyyymmdd)) {\n throw new Error('Invalid date format. Expected YYYYMMDD');\n }\n const kstStart = parse(yyyymmdd, 'yyyyMMdd', new Date());\n const kstEnd = addDays(kstStart, 1);\n // 2. KST → UTC 변환\n const utcStart = fromZonedTime(kstStart, timeZone);\n const utcEnd = fromZonedTime(kstEnd, timeZone);\n return [utcStart, utcEnd];\n}\n\ntype UTCDayRange = {\n label: 'FRIDAY' | 'SATURDAY' | 'SUNDAY';\n utcStart: Date;\n utcEnd: Date;\n};\n\n/**\n * 기준 날짜(yyyymmdd)가 속한 주의\n * 금/토/일 KST 00:00 → UTC Date 반환\n *\n * yyyymmdd가 없으면 현재 시점 기준\n */\nfunction getWeekendUTCStartDates(params?: {\n yyyymmdd?: string;\n}): UTCDayRange[] {\n const baseDate = params?.yyyymmdd ? parse(params.yyyymmdd, 'yyyyMMdd', new Date()) : new Date();\n\n // JS 기준: Sun=0, Mon=1, ... Sat=6\n const baseDay = getDay(baseDate);\n\n // 해당 주의 금요일(5)까지의 offset\n const diffToFriday = (5 - baseDay + 7) % 7;\n\n const kstFridayStart = startOfDay(addDays(baseDate, diffToFriday));\n const kstSaturdayStart = addDays(kstFridayStart, 1);\n const kstSundayStart = addDays(kstFridayStart, 2);\n const kstMondayStart = addDays(kstFridayStart, 3); // Sunday end\n\n return [\n {\n label: 'FRIDAY',\n utcStart: fromZonedTime(kstFridayStart, timeZone),\n utcEnd: fromZonedTime(kstSaturdayStart, timeZone),\n },\n {\n label: 'SATURDAY',\n utcStart: fromZonedTime(kstSaturdayStart, timeZone),\n utcEnd: fromZonedTime(kstSundayStart, timeZone),\n },\n {\n label: 'SUNDAY',\n utcStart: fromZonedTime(kstSundayStart, timeZone),\n utcEnd: fromZonedTime(kstMondayStart, timeZone),\n },\n ];\n}\n\n\nexport const dateUtils = {\n parseEventDate,\n toUTCDayRangeFromYYYYMMDD,\n getWeekendUTCStartDates,\n};\n\n","/**\n * URL을 완전히 디코딩 (이중/삼중 인코딩 모두 해결)\n */\nexport function fullyDecodeURI(uri: string): string {\n let decoded = uri;\n let prevDecoded = '';\n\n // 더 이상 디코딩되지 않을 때까지 반복\n while (decoded !== prevDecoded) {\n prevDecoded = decoded;\n try {\n decoded = decodeURIComponent(decoded);\n } catch (e) {\n // 잘못된 URI 시퀀스면 중단\n break;\n }\n }\n\n return decoded;\n}\n\n/**\n * pathname만 디코딩 (프로토콜, 도메인은 유지)\n */\nexport function fullyDecodePathname(url: string): string {\n try {\n const urlObj = new URL(url);\n urlObj.pathname = fullyDecodeURI(urlObj.pathname);\n return urlObj.toString();\n } catch {\n // URL 파싱 실패 시 전체 문자열 디코딩\n return fullyDecodeURI(url);\n }\n}\n\n/**\n * %가 포함되어 있는지 확인 (인코딩 여부 체크)\n */\nexport function isEncoded(str: string): boolean {\n return str.includes('%');\n}\n\n/**\n * 실제 double-encoding 여부 확인\n * 예: \"%2525\" => true\n * \"%25\" => false (단일 인코딩)\n */\nexport function isDoubleEncoded(str: string): boolean {\n // \"%25\"가 아닌 \"%2525\" 패턴을 찾아야 double-encoding\n return /%25(25)+/i.test(str);\n}\n"],"mappings":"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"}
{"version":3,"file":"index.js","names":["DEFAULT_LOCALES: Record<Lang, string>","s: string","base: string","path: string","value: string","url: string","version: string | undefined","external: boolean","site: SiteConfig","orgId: string","editorId: string","ctx: JsonLdContext","websiteId: string","input: PageSeoInput","work: JsonLdWork","itemReviewed: JsonLdWork","rating: JsonLdRating | undefined","items: { name: string; path: string }[]","event: JsonLdEvent","pageNodes: Thing[]","baseNodes: Thing[]","lang: Lang","link: SeoLink[]","meta: SeoMeta[]","onChange: (e: Event) => void | Promise<void>","token: string","minimum: number","maximum: number","jsonString: string","slug: string","title: string","existingCallback: (newSlug: string) => boolean | Promise<boolean>","newSlug: string","day: number","date: Date","valueToSlugify: string","originalName: string","originalName: string","date: Date","now: Date","eventDate: Date","options?: {\n formatStyle: 'korean' | 'english';\n }","timeFormat","params?: {\n yyyymmdd?: string;\n}","uri: string","url: string","str: string"],"sources":["../src/constants/app-store.constants.ts","../src/constants/common.constants.ts","../src/constants/play-store.constants.ts","../src/constants/sns.constants.ts","../src/constants/web-service.constants.ts","../src/metadata/core.ts","../src/types/auth.ts","../src/utils/utils.file.ts","../src/utils/utils.jwt.ts","../src/utils/utils.number.ts","../src/utils/utils.uuid.ts","../src/utils/utils.parser.ts","../src/utils/utils.slug.ts","../src/utils/utils.event-category.ts","../src/utils/utils.location-city.ts","../src/utils/utils.date.ts","../src/utils/utils.uri.ts"],"sourcesContent":["export const APP_STORE_ID = '1632802589' as const;\nexport const APP_STORE_URL =\n `https://apps.apple.com/kr/app/coldsurf-%EA%B3%B5%EC%97%B0-%EC%B6%94%EC%B2%9C-%ED%8B%B0%EC%BC%93-%EC%B6%94%EC%B2%9C-%EC%84%9C%EB%B9%84%EC%8A%A4/id${APP_STORE_ID}` as const;\n","export const SERVICE_NAME = 'COLDSURF';\n","export const PLAYSTORE_PACKAGE = 'com.fstvllife.android' as const;\nexport const PLAYSTORE_URL =\n 'https://play.google.com/store/apps/details?id=com.fstvllife.android' as const;\nexport const PLAYSTORE_APP_NAME = 'COLDSURF' as const;\n","export const SNS_LINKS = {\n INSTAGRAM: 'https://www.instagram.com/coldsurf.io',\n X: 'https://x.com/coldsurf_io',\n} as const;\n","export const COLDSURF_WEB_URL = 'https://coldsurf.io';\n","import type {\n Article,\n Book,\n BreadcrumbList,\n Event,\n Graph,\n Movie,\n MusicAlbum,\n MusicEvent,\n MusicRecording,\n Organization,\n Person,\n Review,\n Thing,\n WebSite,\n} from 'schema-dts';\n\nexport type Lang = 'ko' | 'en';\n\nexport type SeoImage = {\n /** Absolute URL or path starting with `/`. */\n path: string;\n width?: number;\n height?: number;\n type?: string;\n alt?: string;\n};\n\nexport type ArticleMeta = {\n /** ISO 8601 timestamp. */\n publishedTime: string;\n /** ISO 8601 timestamp. */\n modifiedTime?: string;\n author?: string;\n section?: string;\n tags?: string[];\n};\n\n/**\n * apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다.\n * `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등).\n */\nexport type CreativeWorkKind =\n | 'albums'\n | 'tracks'\n | 'concerts'\n | 'films'\n | 'books'\n | 'events';\n\n/** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */\nexport type JsonLdWork = {\n kind: CreativeWorkKind;\n name: string;\n /** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */\n by?: string;\n /** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */\n year?: number | string;\n /** Path(`/...`) 또는 절대 URL. */\n image?: string;\n /** 작품 페이지 path(`/...`) 또는 절대 URL. */\n url?: string;\n /** 외부 출처 — bandcamp · spotify · imdb 등. */\n sameAs?: string[];\n};\n\nexport type JsonLdRating = { value: number; best?: number; worst?: number };\n\nexport type JsonLdEventOffer = {\n price: number;\n currency: string;\n /** Path(`/...`) 또는 절대 URL. */\n url: string;\n /** ISO 8601 — 티켓 오픈 시각. */\n validFrom: string;\n name?: string;\n};\n\n/**\n * 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와\n * 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다.\n */\nexport type JsonLdEvent = {\n name: string;\n /** 이벤트 페이지 path(`/...`) 또는 절대 URL. */\n url: string;\n /** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */\n startDate: string;\n /** ISO 8601 */\n endDate?: string;\n venue: {\n name: string;\n address: string;\n latitude: number;\n longitude: number;\n };\n /** Path(`/...`) 또는 절대 URL 목록. */\n images?: string[];\n description?: string;\n offers?: JsonLdEventOffer[];\n /** 주최자명. 미지정 시 `venue.name` 으로 대체. */\n organizer?: string;\n};\n\n/**\n * 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다.\n * publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다.\n */\nexport type JsonLd =\n | { type: 'WebSite' }\n /** `input.article` 에서 파생 — article 없으면 무시. */\n | { type: 'Article' }\n | { type: 'Review'; itemReviewed: JsonLdWork; rating?: JsonLdRating }\n | { type: 'CreativeWork'; item: JsonLdWork }\n /** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */\n | { type: 'EventPage'; event: JsonLdEvent }\n | { type: 'Breadcrumb'; items: { name: string; path: string }[] };\n\nexport type PageSeoInput = {\n title: string;\n description: string;\n path: string;\n lang?: Lang;\n /** Alternate language versions for this page. Emits hreflang link tags. */\n alternates?: { lang: Lang; path: string }[];\n /** When set, og:type switches to `article` and article:* tags are emitted. */\n article?: ArticleMeta;\n /** Override the default OG/Twitter share image for this page. */\n image?: SeoImage;\n /** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */\n keywords?: string[];\n /** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */\n jsonLd?: JsonLd[];\n /**\n * Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks`\n * 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시.\n */\n appLinks?: { iosUrl?: string; androidUrl?: string };\n};\n\nexport type SiteConfig = {\n /** Display name appended to every page title. */\n name: string;\n /** Origin used to resolve absolute URLs. Trailing slash is normalized. */\n baseUrl: string;\n /** Default OG/Twitter image used when a page does not override it. */\n defaultImage?: SeoImage;\n /** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */\n logoPath?: string;\n /** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */\n locales?: Partial<Record<Lang, string>>;\n /** Site-wide keywords merged into every page's `keywords` meta. */\n keywords?: string[];\n /**\n * Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter\n * image URLs (paths, not external `http` URLs). Bump it when an image is replaced\n * in place under the same filename so social scrapers (Threads/Slack/iMessage 등)\n * treat it as a new URL instead of serving their stale cache.\n */\n assetVersion?: string;\n /** Publishing organization — emitted as the `Organization` JSON-LD node. */\n publisher?: {\n name: string;\n url?: string;\n logoPath?: string;\n sameAs?: string[];\n };\n /** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */\n editor?: { name: string; url?: string; sameAs?: string[] };\n /**\n * 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit.\n * 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다\n * (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다.\n */\n appLinks?: {\n ios?: { appStoreId: string; appName: string };\n android?: { packageName: string; appName?: string };\n };\n};\n\nexport type SeoMeta = { name?: string; property?: string; content: string };\nexport type SeoLink = { rel: string; href: string; hreflang?: string };\nexport type SeoScript = { type: 'application/ld+json'; innerHTML: string };\n\nexport type SeoTags = {\n title: string;\n htmlLang: Lang;\n meta: SeoMeta[];\n link: SeoLink[];\n /** JSON-LD `<script>` tags. Empty unless `input.jsonLd` is set. */\n script: SeoScript[];\n};\n\nconst DEFAULT_LOCALES: Record<Lang, string> = {\n ko: 'ko_KR',\n en: 'en_US',\n};\n\nconst SCHEMA_CONTEXT = 'https://schema.org';\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction joinUrl(base: string, path: string): string {\n const b = base.replace(/\\/$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n return `${b}${p}`;\n}\n\n/** Resolve a value that may be a path(`/...`) or an already-absolute URL. */\nfunction absoluteUrl(base: string, value: string): string {\n return value.startsWith('http') ? value : joinUrl(base, value);\n}\n\n/**\n * Append `?v=<version>` (or `&v=`) for cache-busting. No-op when version is empty.\n * `external` skips the param entirely — we don't control third-party CDN URLs\n * (signed poster URLs could break) and only want to bust our own re-uploaded assets.\n */\nfunction withAssetVersion(\n url: string,\n version: string | undefined,\n external: boolean\n): string {\n if (!version || external) return url;\n return `${url}${url.includes('?') ? '&' : '?'}v=${encodeURIComponent(version)}`;\n}\n\n/**\n * Absolute *page* (route) URL.\n *\n * 과거 Next.js (`trailingSlash: true` + directory-index 호스팅) 시절엔 모든 page URL 에\n * `/` 를 강제로 붙여 canonical 과 sitemap 을 호스트 redirect 와 정합시켰지만, TSS +\n * Cloudflare Workers SSR 로 이관 후엔 라우트가 slash 없이 서빙된다 (`/event/foo` 는 200,\n * `/event/foo/` 는 의도된 canonical 이 아님). 따라서 `absoluteUrl` 결과를 그대로 둔다.\n */\nfunction pageUrl(base: string, value: string): string {\n return absoluteUrl(base, value);\n}\n\ntype JsonLdContext = {\n url: string;\n fullTitle: string;\n imageUrl?: string;\n lang: Lang;\n};\n\nfunction orgNode(site: SiteConfig, orgId: string): Organization | null {\n const p = site.publisher;\n if (!p) return null;\n return {\n '@type': 'Organization',\n '@id': orgId,\n name: p.name,\n url: p.url ?? site.baseUrl.replace(/\\/$/, ''),\n logo: p.logoPath ? joinUrl(site.baseUrl, p.logoPath) : undefined,\n sameAs: p.sameAs,\n } satisfies Organization;\n}\n\nfunction personNode(site: SiteConfig, editorId: string): Person | null {\n const e = site.editor;\n if (!e) return null;\n return {\n '@type': 'Person',\n '@id': editorId,\n name: e.name,\n url: e.url,\n sameAs: e.sameAs,\n } satisfies Person;\n}\n\nfunction websiteNode(\n site: SiteConfig,\n ctx: JsonLdContext,\n websiteId: string,\n orgId: string\n): WebSite {\n return {\n '@type': 'WebSite',\n '@id': websiteId,\n name: site.name,\n url: site.baseUrl.replace(/\\/$/, ''),\n inLanguage: ctx.lang,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n } satisfies WebSite;\n}\n\nfunction articleNode(\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext,\n orgId: string,\n editorId: string\n): Article | null {\n const a = input.article;\n if (!a) return null;\n return {\n '@type': 'BlogPosting',\n headline: input.title,\n description: input.description,\n url: ctx.url,\n mainEntityOfPage: ctx.url,\n inLanguage: ctx.lang,\n datePublished: a.publishedTime,\n dateModified: a.modifiedTime,\n image: ctx.imageUrl,\n articleSection: a.section,\n keywords: a.tags,\n author: site.editor\n ? { '@id': editorId }\n : a.author\n ? { '@type': 'Person', name: a.author }\n : undefined,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n } satisfies Article;\n}\n\n/**\n * `JsonLdWork` → schema.org 작품 노드. kind 에 따라 @type 과 creator 프로퍼티를 매핑한다.\n * creator(`by`)는 문자열이 아니라 `MusicGroup`/`Person` 객체로 감싸야 schema-dts 가 받는다.\n */\nfunction workNode(work: JsonLdWork, base: string): Thing {\n const name = work.name;\n const image = work.image ? absoluteUrl(base, work.image) : undefined;\n const url = work.url ? pageUrl(base, work.url) : undefined;\n const sameAs = work.sameAs?.length ? work.sameAs : undefined;\n const year = work.year != null ? String(work.year) : undefined;\n const musicGroup = work.by\n ? ({ '@type': 'MusicGroup', name: work.by } as const)\n : undefined;\n const person = work.by\n ? ({ '@type': 'Person', name: work.by } as const)\n : undefined;\n\n switch (work.kind) {\n case 'albums':\n return {\n '@type': 'MusicAlbum',\n name,\n byArtist: musicGroup,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies MusicAlbum;\n case 'tracks':\n return {\n '@type': 'MusicRecording',\n name,\n byArtist: musicGroup,\n image,\n url,\n sameAs,\n } satisfies MusicRecording;\n case 'concerts':\n return {\n '@type': 'MusicEvent',\n name,\n performer: musicGroup,\n image,\n url,\n sameAs,\n startDate: year,\n } satisfies MusicEvent;\n case 'events':\n return {\n '@type': 'Event',\n name,\n performer: musicGroup,\n image,\n url,\n sameAs,\n startDate: year,\n } satisfies Event;\n case 'films':\n return {\n '@type': 'Movie',\n name,\n director: person,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies Movie;\n case 'books':\n return {\n '@type': 'Book',\n name,\n author: person,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies Book;\n }\n}\n\nfunction reviewNode(\n itemReviewed: JsonLdWork,\n rating: JsonLdRating | undefined,\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext,\n base: string,\n orgId: string,\n editorId: string\n): Review {\n const a = input.article;\n return {\n '@type': 'Review',\n name: ctx.fullTitle,\n reviewBody: input.description,\n url: ctx.url,\n inLanguage: ctx.lang,\n datePublished: a?.publishedTime,\n author: site.editor\n ? { '@id': editorId }\n : a?.author\n ? { '@type': 'Person', name: a.author }\n : undefined,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n itemReviewed: workNode(itemReviewed, base),\n reviewRating: rating\n ? {\n '@type': 'Rating',\n ratingValue: rating.value,\n bestRating: rating.best,\n worstRating: rating.worst,\n }\n : undefined,\n } satisfies Review;\n}\n\nfunction breadcrumbNode(\n items: { name: string; path: string }[],\n base: string\n): BreadcrumbList {\n return {\n '@type': 'BreadcrumbList',\n itemListElement: items.map((it, i) => ({\n '@type': 'ListItem',\n position: i + 1,\n name: it.name,\n item: pageUrl(base, it.path),\n })),\n } satisfies BreadcrumbList;\n}\n\n/**\n * `JsonLdEvent` → 풀 `MusicEvent` 노드. venue(Place+GeoCoordinates) · offers(Offer) · organizer\n * 까지 채워 Google Event 리치 결과 자격을 충족한다. `@graph` 노드라 `@context` 는 붙이지 않는다.\n */\nfunction eventNode(event: JsonLdEvent, base: string): MusicEvent {\n return {\n '@type': 'MusicEvent',\n name: event.name,\n url: pageUrl(base, event.url),\n startDate: event.startDate,\n endDate: event.endDate,\n eventAttendanceMode: 'https://schema.org/OfflineEventAttendanceMode',\n eventStatus: 'https://schema.org/EventScheduled',\n location: {\n '@type': 'Place',\n name: event.venue.name,\n address: event.venue.address,\n geo: {\n '@type': 'GeoCoordinates',\n latitude: event.venue.latitude,\n longitude: event.venue.longitude,\n },\n },\n image: event.images?.length\n ? event.images.map((img) => absoluteUrl(base, img))\n : undefined,\n description: event.description,\n offers: event.offers?.length\n ? event.offers.map((offer) => ({\n '@type': 'Offer',\n availability: 'https://schema.org/InStock',\n price: offer.price,\n priceCurrency: offer.currency,\n url: absoluteUrl(base, offer.url),\n validFrom: offer.validFrom,\n name: offer.name,\n }))\n : undefined,\n organizer: {\n '@type': 'Organization',\n name: event.organizer ?? event.venue.name,\n },\n } satisfies MusicEvent;\n}\n\n/**\n * 페이지 디스크립터 → 단일 `@graph` JSON-LD `<script>`.\n * publisher/editor base 노드를 항상 prepend 해 페이지 단위로 `@id` 참조가 해소되게 한다.\n */\nfunction buildJsonLd(\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext\n): SeoScript[] {\n const descriptors = input.jsonLd ?? [];\n if (descriptors.length === 0) return [];\n\n const base = site.baseUrl.replace(/\\/$/, '');\n const orgId = `${base}/#org`;\n const editorId = `${base}/#editor`;\n const websiteId = `${base}/#website`;\n\n const pageNodes: Thing[] = [];\n for (const d of descriptors) {\n switch (d.type) {\n case 'WebSite':\n pageNodes.push(websiteNode(site, ctx, websiteId, orgId));\n break;\n case 'Article': {\n const node = articleNode(input, site, ctx, orgId, editorId);\n if (node) pageNodes.push(node);\n break;\n }\n case 'Review':\n pageNodes.push(\n reviewNode(\n d.itemReviewed,\n d.rating,\n input,\n site,\n ctx,\n base,\n orgId,\n editorId\n )\n );\n break;\n case 'CreativeWork':\n pageNodes.push(workNode(d.item, base));\n break;\n case 'EventPage':\n pageNodes.push(eventNode(d.event, base));\n break;\n case 'Breadcrumb':\n pageNodes.push(breadcrumbNode(d.items, base));\n break;\n }\n }\n if (pageNodes.length === 0) return [];\n\n const baseNodes: Thing[] = [];\n const org = orgNode(site, orgId);\n if (org) baseNodes.push(org);\n const person = personNode(site, editorId);\n if (person) baseNodes.push(person);\n\n const graph = {\n '@context': SCHEMA_CONTEXT,\n '@graph': [...baseNodes, ...pageNodes],\n } satisfies Graph;\n\n return [{ type: 'application/ld+json', innerHTML: JSON.stringify(graph) }];\n}\n\nexport function buildSeoTags(input: PageSeoInput, site: SiteConfig): SeoTags {\n const lang: Lang = input.lang ?? 'ko';\n // 라우트 측에서 이미 브랜드 suffix(`… | COLDSURF 공연 정보`, `… — COLDSURF`, `Engineering — COLDSURF`)를\n // 직접 박은 경우, 여기서 다시 `| ${site.name}` 을 더하면 `... | COLDSURF 공연 정보 | COLDSURF` 처럼 두 번 노출된다.\n // title 안에 site.name 토큰이 이미 등장하면 그대로 둔다 — 라우트의 의도를 보존하면서 중복만 제거.\n const titleHasBrand = new RegExp(`\\\\b${escapeRegExp(site.name)}\\\\b`).test(\n input.title\n );\n const fullTitle = titleHasBrand\n ? input.title\n : `${input.title} | ${site.name}`;\n const url = pageUrl(site.baseUrl, input.path);\n const alternates = input.alternates ?? [];\n const locales = { ...DEFAULT_LOCALES, ...site.locales };\n\n const link: SeoLink[] = [{ rel: 'canonical', href: url }];\n if (alternates.length > 0) {\n link.push({ rel: 'alternate', hreflang: lang, href: url });\n for (const alt of alternates) {\n link.push({\n rel: 'alternate',\n hreflang: alt.lang,\n href: pageUrl(site.baseUrl, alt.path),\n });\n }\n // x-default points at the primary (Korean) language when present.\n const xDefault =\n alternates.find((a) => a.lang === 'ko')?.path ?? input.path;\n link.push({\n rel: 'alternate',\n hreflang: 'x-default',\n href: pageUrl(site.baseUrl, xDefault),\n });\n }\n\n const meta: SeoMeta[] = [\n { name: 'description', content: input.description },\n { name: 'robots', content: 'index, follow' },\n { property: 'og:title', content: fullTitle },\n { property: 'og:description', content: input.description },\n { property: 'og:type', content: input.article ? 'article' : 'website' },\n { property: 'og:url', content: url },\n { property: 'og:site_name', content: site.name },\n { property: 'og:locale', content: locales[lang] },\n ];\n\n const keywords = [\n ...new Set([...(input.keywords ?? []), ...(site.keywords ?? [])]),\n ];\n if (keywords.length > 0) {\n meta.push({ name: 'keywords', content: keywords.join(', ') });\n }\n\n const image = input.image ?? site.defaultImage;\n // SeoImage.path 는 doc 상 \"절대 URL 또는 `/` 시작 path\" — venue 포스터처럼 외부 CDN 풀\n // URL 도 받기 위해 absoluteUrl(http 접두면 그대로) 로 통과시킨다.\n // 자체 도메인 자산만 `?v=` cache-bust — 외부 http URL 은 건드리지 않는다.\n const imageUrl = image\n ? withAssetVersion(\n absoluteUrl(site.baseUrl, image.path),\n site.assetVersion,\n image.path.startsWith('http')\n )\n : undefined;\n if (image && imageUrl) {\n meta.push({ property: 'og:image', content: imageUrl });\n if (image.type)\n meta.push({ property: 'og:image:type', content: image.type });\n if (image.width)\n meta.push({ property: 'og:image:width', content: String(image.width) });\n if (image.height)\n meta.push({ property: 'og:image:height', content: String(image.height) });\n if (image.alt) meta.push({ property: 'og:image:alt', content: image.alt });\n meta.push({ name: 'twitter:image', content: imageUrl });\n if (image.alt) meta.push({ name: 'twitter:image:alt', content: image.alt });\n }\n\n if (site.logoPath) {\n meta.push({\n property: 'og:logo',\n content: joinUrl(site.baseUrl, site.logoPath),\n });\n }\n\n // summary_large_image (1.91:1) 에서 잘리지 않을 만큼 가로로 긴 이미지일 때만 큰 카드로 노출.\n // 정사각형 이하 비율은 summary (작은 카드)로 떨어뜨려 안전하게 보여준다.\n const isWideImage =\n !!image &&\n !!image.width &&\n !!image.height &&\n image.width >= image.height * 1.5;\n const twitterCard = isWideImage ? 'summary_large_image' : 'summary';\n meta.push(\n { name: 'twitter:card', content: twitterCard },\n { name: 'twitter:title', content: fullTitle },\n { name: 'twitter:description', content: input.description }\n );\n\n for (const alt of alternates) {\n if (alt.lang !== lang) {\n meta.push({\n property: 'og:locale:alternate',\n content: locales[alt.lang],\n });\n }\n }\n\n if (input.article) {\n meta.push({\n property: 'article:published_time',\n content: input.article.publishedTime,\n });\n if (input.article.modifiedTime) {\n meta.push({\n property: 'article:modified_time',\n content: input.article.modifiedTime,\n });\n }\n if (input.article.author) {\n meta.push({ property: 'article:author', content: input.article.author });\n }\n if (input.article.section) {\n meta.push({\n property: 'article:section',\n content: input.article.section,\n });\n }\n for (const tag of input.article.tags ?? []) {\n meta.push({ property: 'article:tag', content: tag });\n }\n }\n\n // App Links (al:*) — 앱 아이덴티티는 site, 딥링크 URL 은 페이지에서. Twitter app 카드는 의도적으로\n // 쓰지 않는다(이미지 카드 우선) — al:* 는 카드 종류와 무관하게 네이티브 앱 오픈을 동작시킨다.\n if (site.appLinks?.ios) {\n meta.push({\n property: 'al:ios:app_store_id',\n content: site.appLinks.ios.appStoreId,\n });\n meta.push({\n property: 'al:ios:app_name',\n content: site.appLinks.ios.appName,\n });\n if (input.appLinks?.iosUrl) {\n meta.push({ property: 'al:ios:url', content: input.appLinks.iosUrl });\n }\n }\n if (site.appLinks?.android) {\n meta.push({\n property: 'al:android:package',\n content: site.appLinks.android.packageName,\n });\n if (site.appLinks.android.appName) {\n meta.push({\n property: 'al:android:app_name',\n content: site.appLinks.android.appName,\n });\n }\n if (input.appLinks?.androidUrl) {\n meta.push({\n property: 'al:android:url',\n content: input.appLinks.androidUrl,\n });\n }\n }\n\n const script = buildJsonLd(input, site, { url, fullTitle, imageUrl, lang });\n\n return { title: fullTitle, htmlLang: lang, meta, link, script };\n}\n\n/**\n * `SiteConfig` 를 한 번 바인딩해 페이지 입력만 받는 `buildTags(input)` 를 만든다. 구\n * `NextMetadataGenerator` 의 \"생성자 1회 주입\" 사용감을 stateful 클래스 없이 순수 함수로 재현한다.\n *\n * const seo = createSeo(siteConfig)\n * seo.buildTags({ title, description, path, jsonLd: [...] })\n */\nexport function createSeo(site: SiteConfig) {\n return {\n buildTags: (input: PageSeoInput): SeoTags => buildSeoTags(input, site),\n };\n}\n","import { z } from 'zod';\n\nexport const loginProviderSchema = z.union([\n z.literal('google'),\n z.literal('apple'),\n z.literal('email'),\n]);\nexport type LoginProvider = z.infer<typeof loginProviderSchema>;\n","export function pickFile(onChange: (e: Event) => void | Promise<void>) {\n const input = document.createElement('input');\n input.type = 'file';\n input.click();\n input.onchange = async (e) => {\n await onChange(e);\n input.remove();\n };\n}\n","// Import the jwt-decode library\nimport { jwtDecode } from 'jwt-decode';\n\n// Define the structure of the decoded JWT payload (optional)\ninterface JwtPayload {\n sub: string; // Subject (user identifier)\n name: string; // Name of the user\n email: string; // Email of the user\n exp: number; // Expiration time\n [key: string]: unknown; // Any other properties\n}\n\n// Function to parse JWT token\nexport const decodeJwt = (token: string): JwtPayload | null => {\n try {\n // Decode the token\n const decodedToken = jwtDecode<JwtPayload>(token);\n return decodedToken;\n } catch (error) {\n console.error('Error decoding JWT:', error);\n return null;\n }\n};\n","/**\n * Returns a random integer between the specified values, inclusive.\n * The value is no lower than `min`, and is less than or equal to `max`.\n *\n * @param {number} minimum - The smallest integer value that can be returned, inclusive.\n * @param {number} maximum - The largest integer value that can be returned, inclusive.\n * @returns {number} - A random integer between `min` and `max`, inclusive.\n */\nexport function getRandomInt(minimum: number, maximum: number) {\n const min = Math.ceil(minimum);\n const max = Math.floor(maximum);\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n","export function generateUUID() {\n // Public Domain/MIT\n let d = new Date().getTime(); // Timestamp\n let d2 =\n (typeof performance !== 'undefined' &&\n performance.now &&\n performance.now() * 1000) ||\n 0; // Time in microseconds since page-load or 0 if unsupported\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n let r = Math.random() * 16; // random number between 0 and 16\n if (d > 0) {\n // Use timestamp until depleted\n r = ((d + r) % 16) | 0;\n d = Math.floor(d / 16);\n } else {\n // Use microseconds since page-load if supported\n r = ((d2 + r) % 16) | 0;\n d2 = Math.floor(d2 / 16);\n }\n return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n","export interface TryParseOptions<T> {\n fallback?: T;\n silent?: boolean;\n}\n\nexport function tryParse<T = any>(\n jsonString: string,\n { silent = true, fallback }: TryParseOptions<T> = {}\n): T | undefined {\n try {\n return JSON.parse(jsonString) as T;\n } catch (e) {\n if (!silent) {\n console.warn(\n 'JSON parse error, return fallback:',\n e,\n '| input:',\n jsonString\n );\n }\n\n return fallback;\n }\n}\n","import { format } from 'date-fns';\nimport slugify from 'slugify';\n\nexport const getSafeSlug = (slug: string) => {\n return slugify(slug, {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[[\\]*+~.()'\"?!:@,&<>〈〉#]/g, // 특정 특수문자 제거\n });\n};\n\n// Function to generate unique slugs\nexport async function generateSlug(\n title: string,\n existingCallback: (newSlug: string) => boolean | Promise<boolean>\n) {\n let slug = createSlug(title);\n\n // Check for existing slugs in the database\n let existing = await existingCallback(slug);\n\n // // If slug already exists, append a number\n if (existing) {\n let counter = 1;\n let newSlug: string;\n do {\n newSlug = `${slug}-${counter}`;\n existing = await existingCallback(newSlug);\n counter++;\n } while (existing);\n slug = newSlug;\n }\n\n return slug;\n}\n\nconst replacements = [\n [/#/g, 'no'],\n [/&/g, 'and'],\n [/%/g, 'percent'],\n] as const;\n\nfunction preprocess(title: string) {\n return replacements.reduce(\n (acc, [regex, value]) => acc.replace(regex, value),\n title\n );\n}\n\n// 서수 접미사 함수\nfunction getOrdinalSuffix(day: number): string {\n if (day > 3 && day < 21) return 'th';\n switch (day % 10) {\n case 1:\n return 'st';\n case 2:\n return 'nd';\n case 3:\n return 'rd';\n default:\n return 'th';\n }\n}\n\nfunction formatDateSlug(date: Date): string {\n const day = Number(format(date, 'd')); // 1~31\n const month = format(date, 'MMM').toLowerCase(); // \"Oct\" → \"oct\"\n const ordinal = getOrdinalSuffix(day);\n return `${day}${ordinal}-${month}`;\n}\n\nexport const createSlug = (valueToSlugify: string) => {\n const slug = slugify(preprocess(`${valueToSlugify}`), {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n return slug;\n};\n\nexport const createSlugHashtag = (valueToSlugify: string) => {\n const slug = slugify(preprocess(`${valueToSlugify}`), {\n replacement: '_', // 공백을 \"_\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n return slug;\n};\n\n// Function to generate unique slugs\nexport const createConcertSlug = ({\n title,\n date,\n venueName,\n area,\n}: {\n title: string;\n date: Date;\n venueName?: string;\n area?: string;\n}) => {\n let value = `${title}-${formatDateSlug(date)}`;\n if (venueName) {\n value += `-${venueName}`;\n }\n if (area) {\n value += `-${area}`;\n }\n value += '-티켓';\n const slug = slugify(preprocess(`${value}`), {\n replacement: '-', // 공백을 \"-\"로 변환\n lower: true, // 소문자로 변환\n strict: false, // 특수 문자 제거\n remove: /[\\/[\\]*+~.()'\"?!:@,<>〈〉]/g, // 특정 특수문자 제거\n });\n\n return slug;\n};\n\n// Function to generate unique slugs\nexport async function generateConcertSlug(\n { title, date, venueName, area }: Parameters<typeof createConcertSlug>[0],\n existingCallback: (newSlug: string) => boolean | Promise<boolean>\n) {\n let slug = createConcertSlug({ title, date, venueName, area });\n\n // Check for existing slugs in the database\n let existing = await existingCallback(slug);\n\n // // If slug already exists, append a number\n if (existing) {\n let counter = 1;\n let newSlug: string;\n do {\n newSlug = `${slug}-${counter}`;\n existing = await existingCallback(newSlug);\n counter++;\n } while (existing);\n slug = newSlug;\n }\n\n return slug;\n}\n","const getEventCategoryUIName = (originalName: string) => {\n switch (originalName) {\n case 'Gigs':\n return '콘서트';\n case 'Theatre':\n return '연극 / 뮤지컬';\n case 'Dance':\n return '무용';\n case 'Korean-Traditional':\n return '국악';\n case 'Classic':\n return '클래식';\n case 'Party':\n return '파티 / 오프라인';\n case 'Dj':\n return '디제잉';\n default:\n return originalName;\n }\n};\n\nexport const eventCategoryUtils = {\n getEventCategoryUIName,\n};\n","const getLocationCityUIName = (originalName: string) => {\n switch (originalName.toLowerCase()) {\n case 'seoul':\n return '서울';\n case 'incheon':\n return '인천';\n case 'yeongjongdo':\n return '영종도';\n case 'ulsan':\n return '울산';\n case 'busan':\n return '부산';\n case 'daegu':\n return '대구';\n case 'jeju':\n return '제주';\n case 'gyeongsangbuk-do':\n return '경상북도';\n case 'gyeongsangnam-do':\n return '경상남도';\n case 'gwangju':\n return '광주';\n case 'daejeon':\n return '대전';\n case 'sejong-city':\n return '세종시';\n case 'gyeonggi-do':\n return '경기도';\n case 'gangwon-do':\n return '강원도';\n case 'chungcheongbuk-do':\n return '충청북도';\n case 'chungcheongnam-do':\n return '충청남도';\n case 'jeollabuk-do':\n return '전라북도';\n case 'jeollanam-do':\n return '전라남도';\n case 'tokyo':\n return '도쿄';\n case 'osaka':\n return '오사카';\n case 'hochiminh':\n return '호치민';\n default:\n return originalName;\n }\n};\n\nexport const locationCityUtils = {\n getLocationCityUIName,\n};\n","import {\n addDays,\n format,\n getDay,\n isSameYear,\n parse,\n startOfDay,\n} from 'date-fns';\nimport { fromZonedTime } from 'date-fns-tz';\nimport { toZonedTime } from 'date-fns-tz';\nimport { enUS, ko } from 'date-fns/locale';\n\nconst timeZone = 'Asia/Seoul';\n\nfunction isDifferentYear(date: Date, now: Date = new Date()) {\n return !isSameYear(date, now);\n}\n\nfunction parseEventDate(\n eventDate: Date,\n options?: {\n formatStyle: 'korean' | 'english';\n }\n) {\n const zonedDate = toZonedTime(eventDate, timeZone);\n const minutes = zonedDate.getMinutes();\n if (options?.formatStyle === 'english') {\n const timeFormat = (() => {\n if (isDifferentYear(zonedDate)) {\n return 'MMM dd, yyyy, h:mm a';\n }\n return 'MMM dd, h:mm a';\n })();\n return format(zonedDate, timeFormat, { locale: enUS });\n }\n const timeFormat = (() => {\n if (isDifferentYear(zonedDate)) {\n return minutes === 0\n ? 'EEEE a h시, yyyy년 MMMM d일'\n : 'EEEE a h시 m분, yyyy년 MMMM d일';\n }\n return minutes === 0 ? 'EEEE a h시, MMMM d일' : 'EEEE a h시 m분, MMMM d일';\n })();\n return format(zonedDate, timeFormat, { locale: ko });\n}\n\nfunction toUTCDayRangeFromYYYYMMDD({\n yyyymmdd,\n}: {\n yyyymmdd: string;\n}) {\n if (!/^\\d{8}$/.test(yyyymmdd)) {\n throw new Error('Invalid date format. Expected YYYYMMDD');\n }\n const kstStart = parse(yyyymmdd, 'yyyyMMdd', new Date());\n const kstEnd = addDays(kstStart, 1);\n // 2. KST → UTC 변환\n const utcStart = fromZonedTime(kstStart, timeZone);\n const utcEnd = fromZonedTime(kstEnd, timeZone);\n return [utcStart, utcEnd];\n}\n\ntype UTCDayRange = {\n label: 'FRIDAY' | 'SATURDAY' | 'SUNDAY';\n utcStart: Date;\n utcEnd: Date;\n};\n\n/**\n * 기준 날짜(yyyymmdd)가 속한 주의\n * 금/토/일 KST 00:00 → UTC Date 반환\n *\n * yyyymmdd가 없으면 현재 시점 기준\n */\nfunction getWeekendUTCStartDates(params?: {\n yyyymmdd?: string;\n}): UTCDayRange[] {\n const baseDate = params?.yyyymmdd\n ? parse(params.yyyymmdd, 'yyyyMMdd', new Date())\n : new Date();\n\n // JS 기준: Sun=0, Mon=1, ... Sat=6\n const baseDay = getDay(baseDate);\n\n // 해당 주의 금요일(5)까지의 offset\n const diffToFriday = (5 - baseDay + 7) % 7;\n\n const kstFridayStart = startOfDay(addDays(baseDate, diffToFriday));\n const kstSaturdayStart = addDays(kstFridayStart, 1);\n const kstSundayStart = addDays(kstFridayStart, 2);\n const kstMondayStart = addDays(kstFridayStart, 3); // Sunday end\n\n return [\n {\n label: 'FRIDAY',\n utcStart: fromZonedTime(kstFridayStart, timeZone),\n utcEnd: fromZonedTime(kstSaturdayStart, timeZone),\n },\n {\n label: 'SATURDAY',\n utcStart: fromZonedTime(kstSaturdayStart, timeZone),\n utcEnd: fromZonedTime(kstSundayStart, timeZone),\n },\n {\n label: 'SUNDAY',\n utcStart: fromZonedTime(kstSundayStart, timeZone),\n utcEnd: fromZonedTime(kstMondayStart, timeZone),\n },\n ];\n}\n\nexport const dateUtils = {\n parseEventDate,\n toUTCDayRangeFromYYYYMMDD,\n getWeekendUTCStartDates,\n};\n","/**\n * URL을 완전히 디코딩 (이중/삼중 인코딩 모두 해결)\n */\nexport function fullyDecodeURI(uri: string): string {\n let decoded = uri;\n let prevDecoded = '';\n\n // 더 이상 디코딩되지 않을 때까지 반복\n while (decoded !== prevDecoded) {\n prevDecoded = decoded;\n try {\n decoded = decodeURIComponent(decoded);\n } catch {\n // 잘못된 URI 시퀀스면 중단\n break;\n }\n }\n\n return decoded;\n}\n\n/**\n * pathname만 디코딩 (프로토콜, 도메인은 유지)\n */\nexport function fullyDecodePathname(url: string): string {\n try {\n const urlObj = new URL(url);\n urlObj.pathname = fullyDecodeURI(urlObj.pathname);\n return urlObj.toString();\n } catch {\n // URL 파싱 실패 시 전체 문자열 디코딩\n return fullyDecodeURI(url);\n }\n}\n\n/**\n * %가 포함되어 있는지 확인 (인코딩 여부 체크)\n */\nexport function isEncoded(str: string): boolean {\n return str.includes('%');\n}\n\n/**\n * 실제 double-encoding 여부 확인\n * 예: \"%2525\" => true\n * \"%25\" => false (단일 인코딩)\n */\nexport function isDoubleEncoded(str: string): boolean {\n // \"%25\"가 아닌 \"%2525\" 패턴을 찾아야 double-encoding\n return /%25(25)+/i.test(str);\n}\n"],"mappings":"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"}

@@ -1,1 +0,1 @@

var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`react`));function l(e,t){let n=(0,c.useRef)(e),r=(0,c.useRef)(null);return(0,c.useEffect)(()=>{n.current=e}),(0,c.useCallback)((...e)=>{r.current&&clearTimeout(r.current),r.current=setTimeout(()=>n.current(...e),t)},[t])}exports.useDebouncedCallback=l;
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`react`)),l={ko:`ko_KR`,en:`en_US`},u=`https://schema.org`;function d(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function f(e,t){let n=e.replace(/\/$/,``),r=t.startsWith(`/`)?t:`/${t}`;return`${n}${r}`}function p(e,t){return t.startsWith(`http`)?t:f(e,t)}function m(e,t,n){return!t||n?e:`${e}${e.includes(`?`)?`&`:`?`}v=${encodeURIComponent(t)}`}function h(e,t){return p(e,t)}function g(e,t){let n=e.publisher;return n?{"@type":`Organization`,"@id":t,name:n.name,url:n.url??e.baseUrl.replace(/\/$/,``),logo:n.logoPath?f(e.baseUrl,n.logoPath):void 0,sameAs:n.sameAs}:null}function _(e,t){let n=e.editor;return n?{"@type":`Person`,"@id":t,name:n.name,url:n.url,sameAs:n.sameAs}:null}function v(e,t,n,r){return{"@type":`WebSite`,"@id":n,name:e.name,url:e.baseUrl.replace(/\/$/,``),inLanguage:t.lang,publisher:e.publisher?{"@id":r}:void 0}}function y(e,t,n,r,i){let a=e.article;return a?{"@type":`BlogPosting`,headline:e.title,description:e.description,url:n.url,mainEntityOfPage:n.url,inLanguage:n.lang,datePublished:a.publishedTime,dateModified:a.modifiedTime,image:n.imageUrl,articleSection:a.section,keywords:a.tags,author:t.editor?{"@id":i}:a.author?{"@type":`Person`,name:a.author}:void 0,publisher:t.publisher?{"@id":r}:void 0}:null}function b(e,t){let n=e.name,r=e.image?p(t,e.image):void 0,i=e.url?h(t,e.url):void 0,a=e.sameAs?.length?e.sameAs:void 0,o=e.year==null?void 0:String(e.year),s=e.by?{"@type":`MusicGroup`,name:e.by}:void 0,c=e.by?{"@type":`Person`,name:e.by}:void 0;switch(e.kind){case`albums`:return{"@type":`MusicAlbum`,name:n,byArtist:s,image:r,url:i,sameAs:a,datePublished:o};case`tracks`:return{"@type":`MusicRecording`,name:n,byArtist:s,image:r,url:i,sameAs:a};case`concerts`:return{"@type":`MusicEvent`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`events`:return{"@type":`Event`,name:n,performer:s,image:r,url:i,sameAs:a,startDate:o};case`films`:return{"@type":`Movie`,name:n,director:c,image:r,url:i,sameAs:a,datePublished:o};case`books`:return{"@type":`Book`,name:n,author:c,image:r,url:i,sameAs:a,datePublished:o}}}function x(e,t,n,r,i,a,o,s){let c=n.article;return{"@type":`Review`,name:i.fullTitle,reviewBody:n.description,url:i.url,inLanguage:i.lang,datePublished:c?.publishedTime,author:r.editor?{"@id":s}:c?.author?{"@type":`Person`,name:c.author}:void 0,publisher:r.publisher?{"@id":o}:void 0,itemReviewed:b(e,a),reviewRating:t?{"@type":`Rating`,ratingValue:t.value,bestRating:t.best,worstRating:t.worst}:void 0}}function S(e,t){return{"@type":`BreadcrumbList`,itemListElement:e.map((e,n)=>({"@type":`ListItem`,position:n+1,name:e.name,item:h(t,e.path)}))}}function C(e,t){return{"@type":`MusicEvent`,name:e.name,url:h(t,e.url),startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}},image:e.images?.length?e.images.map(e=>p(t,e)):void 0,description:e.description,offers:e.offers?.length?e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:p(t,e.url),validFrom:e.validFrom,name:e.name})):void 0,organizer:{"@type":`Organization`,name:e.organizer??e.venue.name}}}function w(e,t,n){let r=e.jsonLd??[];if(r.length===0)return[];let i=t.baseUrl.replace(/\/$/,``),a=`${i}/#org`,o=`${i}/#editor`,s=`${i}/#website`,c=[];for(let l of r)switch(l.type){case`WebSite`:c.push(v(t,n,s,a));break;case`Article`:{let r=y(e,t,n,a,o);r&&c.push(r);break}case`Review`:c.push(x(l.itemReviewed,l.rating,e,t,n,i,a,o));break;case`CreativeWork`:c.push(b(l.item,i));break;case`EventPage`:c.push(C(l.event,i));break;case`Breadcrumb`:c.push(S(l.items,i));break}if(c.length===0)return[];let l=[],d=g(t,a);d&&l.push(d);let f=_(t,o);f&&l.push(f);let p={"@context":u,"@graph":[...l,...c]};return[{type:`application/ld+json`,innerHTML:JSON.stringify(p)}]}function T(e,t){let n=e.lang??`ko`,r=RegExp(`\\b${d(t.name)}\\b`).test(e.title),i=r?e.title:`${e.title} | ${t.name}`,a=h(t.baseUrl,e.path),o=e.alternates??[],s={...l,...t.locales},c=[{rel:`canonical`,href:a}];if(o.length>0){c.push({rel:`alternate`,hreflang:n,href:a});for(let e of o)c.push({rel:`alternate`,hreflang:e.lang,href:h(t.baseUrl,e.path)});let r=o.find(e=>e.lang===`ko`)?.path??e.path;c.push({rel:`alternate`,hreflang:`x-default`,href:h(t.baseUrl,r)})}let u=[{name:`description`,content:e.description},{name:`robots`,content:`index, follow`},{property:`og:title`,content:i},{property:`og:description`,content:e.description},{property:`og:type`,content:e.article?`article`:`website`},{property:`og:url`,content:a},{property:`og:site_name`,content:t.name},{property:`og:locale`,content:s[n]}],g=[...new Set([...e.keywords??[],...t.keywords??[]])];g.length>0&&u.push({name:`keywords`,content:g.join(`, `)});let _=e.image??t.defaultImage,v=_?m(p(t.baseUrl,_.path),t.assetVersion,_.path.startsWith(`http`)):void 0;_&&v&&(u.push({property:`og:image`,content:v}),_.type&&u.push({property:`og:image:type`,content:_.type}),_.width&&u.push({property:`og:image:width`,content:String(_.width)}),_.height&&u.push({property:`og:image:height`,content:String(_.height)}),_.alt&&u.push({property:`og:image:alt`,content:_.alt}),u.push({name:`twitter:image`,content:v}),_.alt&&u.push({name:`twitter:image:alt`,content:_.alt})),t.logoPath&&u.push({property:`og:logo`,content:f(t.baseUrl,t.logoPath)});let y=!!_&&!!_.width&&!!_.height&&_.width>=_.height*1.5,b=y?`summary_large_image`:`summary`;u.push({name:`twitter:card`,content:b},{name:`twitter:title`,content:i},{name:`twitter:description`,content:e.description});for(let e of o)e.lang!==n&&u.push({property:`og:locale:alternate`,content:s[e.lang]});if(e.article){u.push({property:`article:published_time`,content:e.article.publishedTime}),e.article.modifiedTime&&u.push({property:`article:modified_time`,content:e.article.modifiedTime}),e.article.author&&u.push({property:`article:author`,content:e.article.author}),e.article.section&&u.push({property:`article:section`,content:e.article.section});for(let t of e.article.tags??[])u.push({property:`article:tag`,content:t})}t.appLinks?.ios&&(u.push({property:`al:ios:app_store_id`,content:t.appLinks.ios.appStoreId}),u.push({property:`al:ios:app_name`,content:t.appLinks.ios.appName}),e.appLinks?.iosUrl&&u.push({property:`al:ios:url`,content:e.appLinks.iosUrl})),t.appLinks?.android&&(u.push({property:`al:android:package`,content:t.appLinks.android.packageName}),t.appLinks.android.appName&&u.push({property:`al:android:app_name`,content:t.appLinks.android.appName}),e.appLinks?.androidUrl&&u.push({property:`al:android:url`,content:e.appLinks.androidUrl}));let x=w(e,t,{url:a,fullTitle:i,imageUrl:v,lang:n});return{title:i,htmlLang:n,meta:u,link:c,script:x}}function E(e,t){let n=(0,c.useRef)(e),r=(0,c.useRef)(null);return(0,c.useEffect)(()=>{n.current=e}),(0,c.useCallback)((...e)=>{r.current&&clearTimeout(r.current),r.current=setTimeout(()=>n.current(...e),t)},[t])}function D(e){return t=>{let{robots:n,...r}=t,i=T(r,e),a=[{title:i.title}];for(let e of i.meta)if(e.name){let t=e.name===`robots`&&n?n:e.content;a.push({name:e.name,content:t})}else e.property&&a.push({property:e.property,content:e.content});let o=i.link.map(e=>({rel:e.rel,href:e.href,...e.hreflang?{hreflang:e.hreflang}:{}})),s=i.script.map(e=>({type:e.type,children:e.innerHTML}));return{meta:a,links:o,scripts:s}}}exports.createSeoHead=D,exports.useDebouncedCallback=E;

@@ -0,7 +1,236 @@

//#region src/metadata/core.d.ts
type Lang = 'ko' | 'en';
type SeoImage = {
/** Absolute URL or path starting with `/`. */
path: string;
width?: number;
height?: number;
type?: string;
alt?: string;
};
type ArticleMeta = {
/** ISO 8601 timestamp. */
publishedTime: string;
/** ISO 8601 timestamp. */
modifiedTime?: string;
author?: string;
section?: string;
tags?: string[];
};
/**
* apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다.
* `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등).
*/
type CreativeWorkKind = 'albums' | 'tracks' | 'concerts' | 'films' | 'books' | 'events';
/** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */
type JsonLdWork = {
kind: CreativeWorkKind;
name: string;
/** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */
by?: string;
/** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */
year?: number | string;
/** Path(`/...`) 또는 절대 URL. */
image?: string;
/** 작품 페이지 path(`/...`) 또는 절대 URL. */
url?: string;
/** 외부 출처 — bandcamp · spotify · imdb 등. */
sameAs?: string[];
};
type JsonLdRating = {
value: number;
best?: number;
worst?: number;
};
type JsonLdEventOffer = {
price: number;
currency: string;
/** Path(`/...`) 또는 절대 URL. */
url: string;
/** ISO 8601 — 티켓 오픈 시각. */
validFrom: string;
name?: string;
};
/**
* 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와
* 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다.
*/
type JsonLdEvent = {
name: string;
/** 이벤트 페이지 path(`/...`) 또는 절대 URL. */
url: string;
/** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */
startDate: string;
/** ISO 8601 */
endDate?: string;
venue: {
name: string;
address: string;
latitude: number;
longitude: number;
};
/** Path(`/...`) 또는 절대 URL 목록. */
images?: string[];
description?: string;
offers?: JsonLdEventOffer[];
/** 주최자명. 미지정 시 `venue.name` 으로 대체. */
organizer?: string;
};
/**
* 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다.
* publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다.
*/
type JsonLd = {
type: 'WebSite';
}
/** `input.article` 에서 파생 — article 없으면 무시. */ | {
type: 'Article';
} | {
type: 'Review';
itemReviewed: JsonLdWork;
rating?: JsonLdRating;
} | {
type: 'CreativeWork';
item: JsonLdWork;
}
/** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */ | {
type: 'EventPage';
event: JsonLdEvent;
} | {
type: 'Breadcrumb';
items: {
name: string;
path: string;
}[];
};
type PageSeoInput = {
title: string;
description: string;
path: string;
lang?: Lang;
/** Alternate language versions for this page. Emits hreflang link tags. */
alternates?: {
lang: Lang;
path: string;
}[];
/** When set, og:type switches to `article` and article:* tags are emitted. */
article?: ArticleMeta;
/** Override the default OG/Twitter share image for this page. */
image?: SeoImage;
/** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */
keywords?: string[];
/** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */
jsonLd?: JsonLd[];
/**
* Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks`
* 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시.
*/
appLinks?: {
iosUrl?: string;
androidUrl?: string;
};
};
type SiteConfig = {
/** Display name appended to every page title. */
name: string;
/** Origin used to resolve absolute URLs. Trailing slash is normalized. */
baseUrl: string;
/** Default OG/Twitter image used when a page does not override it. */
defaultImage?: SeoImage;
/** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */
logoPath?: string;
/** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */
locales?: Partial<Record<Lang, string>>;
/** Site-wide keywords merged into every page's `keywords` meta. */
keywords?: string[];
/**
* Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter
* image URLs (paths, not external `http` URLs). Bump it when an image is replaced
* in place under the same filename so social scrapers (Threads/Slack/iMessage 등)
* treat it as a new URL instead of serving their stale cache.
*/
assetVersion?: string;
/** Publishing organization — emitted as the `Organization` JSON-LD node. */
publisher?: {
name: string;
url?: string;
logoPath?: string;
sameAs?: string[];
};
/** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */
editor?: {
name: string;
url?: string;
sameAs?: string[];
};
/**
* 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit.
* 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다
* (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다.
*/
appLinks?: {
ios?: {
appStoreId: string;
appName: string;
};
android?: {
packageName: string;
appName?: string;
};
};
};
//#endregion
//#region src/react/index.d.ts
declare function useDebouncedCallback<T extends (...args: any[]) => void>(fn: T, delay: number): (...args: Parameters<T>) => void;
//# sourceMappingURL=index.d.ts.map
type HeadMeta = {
title: string;
} | {
charSet: string;
} | {
name: string;
content: string;
} | {
property: string;
content: string;
} | {
httpEquiv: string;
content: string;
};
type HeadLink = {
rel: string;
href: string;
hreflang?: string;
type?: string;
sizes?: string;
};
type HeadScript = {
children?: string;
src?: string;
type?: string;
async?: boolean;
};
type SeoHead = {
meta: HeadMeta[];
links: HeadLink[];
scripts: HeadScript[];
};
/** `PageSeoInput` + `robots` override. 미지정 시 엔진 기본값 `'index, follow'`. */
type SeoHeadInput = PageSeoInput & {
robots?: string;
};
/**
* `SiteConfig` 를 한 번 바인딩해, 라우트에서 단일 declarative 입력으로 호출하는 `seoHead(input)` 를 만든다.
*
* // app 진입 1회
* export const seoHead = createSeoHead({ ...SITE, baseUrl })
*
* // 라우트
* head: ({ match }) => seoHead({ path: '/about', lang: match.context.lang, title, description })
* head: () => seoHead({ path: '/resume', lang: 'ko', ...copy, robots: 'noindex, nofollow' })
*
* 반환값(`{ meta, links, scripts }`)을 라우터가 `<head>` 로 직렬화한다.
*/
declare function createSeoHead(site: SiteConfig): (input: SeoHeadInput) => SeoHead;
//#endregion
export { useDebouncedCallback };
export { type ArticleMeta, HeadLink, HeadMeta, HeadScript, type JsonLd, type JsonLdEvent, type JsonLdEventOffer, type JsonLdWork, type Lang, type PageSeoInput, SeoHead, SeoHeadInput, type SeoImage, type SiteConfig, createSeoHead, useDebouncedCallback };
//# sourceMappingURL=index.d.cts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/src/react/index.ts"],"sourcesContent":[],"mappings":";iBAGgB,6DACV,6BAEO,WAAW;AAHxB"}
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/metadata/core.ts","../../src/react/index.ts"],"sourcesContent":[],"mappings":"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"}

@@ -0,7 +1,236 @@

//#region src/metadata/core.d.ts
type Lang = 'ko' | 'en';
type SeoImage = {
/** Absolute URL or path starting with `/`. */
path: string;
width?: number;
height?: number;
type?: string;
alt?: string;
};
type ArticleMeta = {
/** ISO 8601 timestamp. */
publishedTime: string;
/** ISO 8601 timestamp. */
modifiedTime?: string;
author?: string;
section?: string;
tags?: string[];
};
/**
* apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다.
* `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등).
*/
type CreativeWorkKind = 'albums' | 'tracks' | 'concerts' | 'films' | 'books' | 'events';
/** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */
type JsonLdWork = {
kind: CreativeWorkKind;
name: string;
/** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */
by?: string;
/** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */
year?: number | string;
/** Path(`/...`) 또는 절대 URL. */
image?: string;
/** 작품 페이지 path(`/...`) 또는 절대 URL. */
url?: string;
/** 외부 출처 — bandcamp · spotify · imdb 등. */
sameAs?: string[];
};
type JsonLdRating = {
value: number;
best?: number;
worst?: number;
};
type JsonLdEventOffer = {
price: number;
currency: string;
/** Path(`/...`) 또는 절대 URL. */
url: string;
/** ISO 8601 — 티켓 오픈 시각. */
validFrom: string;
name?: string;
};
/**
* 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와
* 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다.
*/
type JsonLdEvent = {
name: string;
/** 이벤트 페이지 path(`/...`) 또는 절대 URL. */
url: string;
/** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */
startDate: string;
/** ISO 8601 */
endDate?: string;
venue: {
name: string;
address: string;
latitude: number;
longitude: number;
};
/** Path(`/...`) 또는 절대 URL 목록. */
images?: string[];
description?: string;
offers?: JsonLdEventOffer[];
/** 주최자명. 미지정 시 `venue.name` 으로 대체. */
organizer?: string;
};
/**
* 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다.
* publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다.
*/
type JsonLd = {
type: 'WebSite';
}
/** `input.article` 에서 파생 — article 없으면 무시. */ | {
type: 'Article';
} | {
type: 'Review';
itemReviewed: JsonLdWork;
rating?: JsonLdRating;
} | {
type: 'CreativeWork';
item: JsonLdWork;
}
/** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */ | {
type: 'EventPage';
event: JsonLdEvent;
} | {
type: 'Breadcrumb';
items: {
name: string;
path: string;
}[];
};
type PageSeoInput = {
title: string;
description: string;
path: string;
lang?: Lang;
/** Alternate language versions for this page. Emits hreflang link tags. */
alternates?: {
lang: Lang;
path: string;
}[];
/** When set, og:type switches to `article` and article:* tags are emitted. */
article?: ArticleMeta;
/** Override the default OG/Twitter share image for this page. */
image?: SeoImage;
/** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */
keywords?: string[];
/** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */
jsonLd?: JsonLd[];
/**
* Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks`
* 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시.
*/
appLinks?: {
iosUrl?: string;
androidUrl?: string;
};
};
type SiteConfig = {
/** Display name appended to every page title. */
name: string;
/** Origin used to resolve absolute URLs. Trailing slash is normalized. */
baseUrl: string;
/** Default OG/Twitter image used when a page does not override it. */
defaultImage?: SeoImage;
/** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */
logoPath?: string;
/** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */
locales?: Partial<Record<Lang, string>>;
/** Site-wide keywords merged into every page's `keywords` meta. */
keywords?: string[];
/**
* Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter
* image URLs (paths, not external `http` URLs). Bump it when an image is replaced
* in place under the same filename so social scrapers (Threads/Slack/iMessage 등)
* treat it as a new URL instead of serving their stale cache.
*/
assetVersion?: string;
/** Publishing organization — emitted as the `Organization` JSON-LD node. */
publisher?: {
name: string;
url?: string;
logoPath?: string;
sameAs?: string[];
};
/** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */
editor?: {
name: string;
url?: string;
sameAs?: string[];
};
/**
* 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit.
* 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다
* (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다.
*/
appLinks?: {
ios?: {
appStoreId: string;
appName: string;
};
android?: {
packageName: string;
appName?: string;
};
};
};
//#endregion
//#region src/react/index.d.ts
declare function useDebouncedCallback<T extends (...args: any[]) => void>(fn: T, delay: number): (...args: Parameters<T>) => void;
//# sourceMappingURL=index.d.ts.map
type HeadMeta = {
title: string;
} | {
charSet: string;
} | {
name: string;
content: string;
} | {
property: string;
content: string;
} | {
httpEquiv: string;
content: string;
};
type HeadLink = {
rel: string;
href: string;
hreflang?: string;
type?: string;
sizes?: string;
};
type HeadScript = {
children?: string;
src?: string;
type?: string;
async?: boolean;
};
type SeoHead = {
meta: HeadMeta[];
links: HeadLink[];
scripts: HeadScript[];
};
/** `PageSeoInput` + `robots` override. 미지정 시 엔진 기본값 `'index, follow'`. */
type SeoHeadInput = PageSeoInput & {
robots?: string;
};
/**
* `SiteConfig` 를 한 번 바인딩해, 라우트에서 단일 declarative 입력으로 호출하는 `seoHead(input)` 를 만든다.
*
* // app 진입 1회
* export const seoHead = createSeoHead({ ...SITE, baseUrl })
*
* // 라우트
* head: ({ match }) => seoHead({ path: '/about', lang: match.context.lang, title, description })
* head: () => seoHead({ path: '/resume', lang: 'ko', ...copy, robots: 'noindex, nofollow' })
*
* 반환값(`{ meta, links, scripts }`)을 라우터가 `<head>` 로 직렬화한다.
*/
declare function createSeoHead(site: SiteConfig): (input: SeoHeadInput) => SeoHead;
//#endregion
export { useDebouncedCallback };
export { type ArticleMeta, HeadLink, HeadMeta, HeadScript, type JsonLd, type JsonLdEvent, type JsonLdEventOffer, type JsonLdWork, type Lang, type PageSeoInput, SeoHead, SeoHeadInput, type SeoImage, type SiteConfig, createSeoHead, useDebouncedCallback };
//# sourceMappingURL=index.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/src/react/index.ts"],"sourcesContent":[],"mappings":";iBAGgB,6DACV,6BAEO,WAAW;AAHxB"}
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/metadata/core.ts","../../src/react/index.ts"],"sourcesContent":[],"mappings":"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"}

@@ -1,2 +0,2 @@

import{useCallback as e,useEffect as t,useRef as n}from"react";function r(r,i){let a=n(r),o=n(null);return t(()=>{a.current=r}),e((...e)=>{o.current&&clearTimeout(o.current),o.current=setTimeout(()=>a.current(...e),i)},[i])}export{r as useDebouncedCallback};
import{useCallback as e,useEffect as t,useRef as n}from"react";const r={ko:`ko_KR`,en:`en_US`},i=`https://schema.org`;function a(e){return e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)}function o(e,t){let n=e.replace(/\/$/,``),r=t.startsWith(`/`)?t:`/${t}`;return`${n}${r}`}function s(e,t){return t.startsWith(`http`)?t:o(e,t)}function c(e,t,n){return!t||n?e:`${e}${e.includes(`?`)?`&`:`?`}v=${encodeURIComponent(t)}`}function l(e,t){return s(e,t)}function u(e,t){let n=e.publisher;return n?{"@type":`Organization`,"@id":t,name:n.name,url:n.url??e.baseUrl.replace(/\/$/,``),logo:n.logoPath?o(e.baseUrl,n.logoPath):void 0,sameAs:n.sameAs}:null}function d(e,t){let n=e.editor;return n?{"@type":`Person`,"@id":t,name:n.name,url:n.url,sameAs:n.sameAs}:null}function f(e,t,n,r){return{"@type":`WebSite`,"@id":n,name:e.name,url:e.baseUrl.replace(/\/$/,``),inLanguage:t.lang,publisher:e.publisher?{"@id":r}:void 0}}function p(e,t,n,r,i){let a=e.article;return a?{"@type":`BlogPosting`,headline:e.title,description:e.description,url:n.url,mainEntityOfPage:n.url,inLanguage:n.lang,datePublished:a.publishedTime,dateModified:a.modifiedTime,image:n.imageUrl,articleSection:a.section,keywords:a.tags,author:t.editor?{"@id":i}:a.author?{"@type":`Person`,name:a.author}:void 0,publisher:t.publisher?{"@id":r}:void 0}:null}function m(e,t){let n=e.name,r=e.image?s(t,e.image):void 0,i=e.url?l(t,e.url):void 0,a=e.sameAs?.length?e.sameAs:void 0,o=e.year==null?void 0:String(e.year),c=e.by?{"@type":`MusicGroup`,name:e.by}:void 0,u=e.by?{"@type":`Person`,name:e.by}:void 0;switch(e.kind){case`albums`:return{"@type":`MusicAlbum`,name:n,byArtist:c,image:r,url:i,sameAs:a,datePublished:o};case`tracks`:return{"@type":`MusicRecording`,name:n,byArtist:c,image:r,url:i,sameAs:a};case`concerts`:return{"@type":`MusicEvent`,name:n,performer:c,image:r,url:i,sameAs:a,startDate:o};case`events`:return{"@type":`Event`,name:n,performer:c,image:r,url:i,sameAs:a,startDate:o};case`films`:return{"@type":`Movie`,name:n,director:u,image:r,url:i,sameAs:a,datePublished:o};case`books`:return{"@type":`Book`,name:n,author:u,image:r,url:i,sameAs:a,datePublished:o}}}function h(e,t,n,r,i,a,o,s){let c=n.article;return{"@type":`Review`,name:i.fullTitle,reviewBody:n.description,url:i.url,inLanguage:i.lang,datePublished:c?.publishedTime,author:r.editor?{"@id":s}:c?.author?{"@type":`Person`,name:c.author}:void 0,publisher:r.publisher?{"@id":o}:void 0,itemReviewed:m(e,a),reviewRating:t?{"@type":`Rating`,ratingValue:t.value,bestRating:t.best,worstRating:t.worst}:void 0}}function g(e,t){return{"@type":`BreadcrumbList`,itemListElement:e.map((e,n)=>({"@type":`ListItem`,position:n+1,name:e.name,item:l(t,e.path)}))}}function _(e,t){return{"@type":`MusicEvent`,name:e.name,url:l(t,e.url),startDate:e.startDate,endDate:e.endDate,eventAttendanceMode:`https://schema.org/OfflineEventAttendanceMode`,eventStatus:`https://schema.org/EventScheduled`,location:{"@type":`Place`,name:e.venue.name,address:e.venue.address,geo:{"@type":`GeoCoordinates`,latitude:e.venue.latitude,longitude:e.venue.longitude}},image:e.images?.length?e.images.map(e=>s(t,e)):void 0,description:e.description,offers:e.offers?.length?e.offers.map(e=>({"@type":`Offer`,availability:`https://schema.org/InStock`,price:e.price,priceCurrency:e.currency,url:s(t,e.url),validFrom:e.validFrom,name:e.name})):void 0,organizer:{"@type":`Organization`,name:e.organizer??e.venue.name}}}function v(e,t,n){let r=e.jsonLd??[];if(r.length===0)return[];let a=t.baseUrl.replace(/\/$/,``),o=`${a}/#org`,s=`${a}/#editor`,c=`${a}/#website`,l=[];for(let i of r)switch(i.type){case`WebSite`:l.push(f(t,n,c,o));break;case`Article`:{let r=p(e,t,n,o,s);r&&l.push(r);break}case`Review`:l.push(h(i.itemReviewed,i.rating,e,t,n,a,o,s));break;case`CreativeWork`:l.push(m(i.item,a));break;case`EventPage`:l.push(_(i.event,a));break;case`Breadcrumb`:l.push(g(i.items,a));break}if(l.length===0)return[];let v=[],y=u(t,o);y&&v.push(y);let b=d(t,s);b&&v.push(b);let x={"@context":i,"@graph":[...v,...l]};return[{type:`application/ld+json`,innerHTML:JSON.stringify(x)}]}function y(e,t){let n=e.lang??`ko`,i=RegExp(`\\b${a(t.name)}\\b`).test(e.title),u=i?e.title:`${e.title} | ${t.name}`,d=l(t.baseUrl,e.path),f=e.alternates??[],p={...r,...t.locales},m=[{rel:`canonical`,href:d}];if(f.length>0){m.push({rel:`alternate`,hreflang:n,href:d});for(let e of f)m.push({rel:`alternate`,hreflang:e.lang,href:l(t.baseUrl,e.path)});let r=f.find(e=>e.lang===`ko`)?.path??e.path;m.push({rel:`alternate`,hreflang:`x-default`,href:l(t.baseUrl,r)})}let h=[{name:`description`,content:e.description},{name:`robots`,content:`index, follow`},{property:`og:title`,content:u},{property:`og:description`,content:e.description},{property:`og:type`,content:e.article?`article`:`website`},{property:`og:url`,content:d},{property:`og:site_name`,content:t.name},{property:`og:locale`,content:p[n]}],g=[...new Set([...e.keywords??[],...t.keywords??[]])];g.length>0&&h.push({name:`keywords`,content:g.join(`, `)});let _=e.image??t.defaultImage,y=_?c(s(t.baseUrl,_.path),t.assetVersion,_.path.startsWith(`http`)):void 0;_&&y&&(h.push({property:`og:image`,content:y}),_.type&&h.push({property:`og:image:type`,content:_.type}),_.width&&h.push({property:`og:image:width`,content:String(_.width)}),_.height&&h.push({property:`og:image:height`,content:String(_.height)}),_.alt&&h.push({property:`og:image:alt`,content:_.alt}),h.push({name:`twitter:image`,content:y}),_.alt&&h.push({name:`twitter:image:alt`,content:_.alt})),t.logoPath&&h.push({property:`og:logo`,content:o(t.baseUrl,t.logoPath)});let b=!!_&&!!_.width&&!!_.height&&_.width>=_.height*1.5,x=b?`summary_large_image`:`summary`;h.push({name:`twitter:card`,content:x},{name:`twitter:title`,content:u},{name:`twitter:description`,content:e.description});for(let e of f)e.lang!==n&&h.push({property:`og:locale:alternate`,content:p[e.lang]});if(e.article){h.push({property:`article:published_time`,content:e.article.publishedTime}),e.article.modifiedTime&&h.push({property:`article:modified_time`,content:e.article.modifiedTime}),e.article.author&&h.push({property:`article:author`,content:e.article.author}),e.article.section&&h.push({property:`article:section`,content:e.article.section});for(let t of e.article.tags??[])h.push({property:`article:tag`,content:t})}t.appLinks?.ios&&(h.push({property:`al:ios:app_store_id`,content:t.appLinks.ios.appStoreId}),h.push({property:`al:ios:app_name`,content:t.appLinks.ios.appName}),e.appLinks?.iosUrl&&h.push({property:`al:ios:url`,content:e.appLinks.iosUrl})),t.appLinks?.android&&(h.push({property:`al:android:package`,content:t.appLinks.android.packageName}),t.appLinks.android.appName&&h.push({property:`al:android:app_name`,content:t.appLinks.android.appName}),e.appLinks?.androidUrl&&h.push({property:`al:android:url`,content:e.appLinks.androidUrl}));let S=v(e,t,{url:d,fullTitle:u,imageUrl:y,lang:n});return{title:u,htmlLang:n,meta:h,link:m,script:S}}function b(r,i){let a=n(r),o=n(null);return t(()=>{a.current=r}),e((...e)=>{o.current&&clearTimeout(o.current),o.current=setTimeout(()=>a.current(...e),i)},[i])}function x(e){return t=>{let{robots:n,...r}=t,i=y(r,e),a=[{title:i.title}];for(let e of i.meta)if(e.name){let t=e.name===`robots`&&n?n:e.content;a.push({name:e.name,content:t})}else e.property&&a.push({property:e.property,content:e.content});let o=i.link.map(e=>({rel:e.rel,href:e.href,...e.hreflang?{hreflang:e.hreflang}:{}})),s=i.script.map(e=>({type:e.type,children:e.innerHTML}));return{meta:a,links:o,scripts:s}}}export{x as createSeoHead,b as useDebouncedCallback};
//# sourceMappingURL=index.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","names":["fn: T","delay: number"],"sources":["../../src/react/index.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\n\n// biome-ignore lint/suspicious/noExplicitAny: generic callback signature\nexport function useDebouncedCallback<T extends (...args: any[]) => void>(\n fn: T,\n delay: number\n): (...args: Parameters<T>) => void {\n const fnRef = useRef(fn);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n fnRef.current = fn;\n });\n\n return useCallback(\n (...args: Parameters<T>) => {\n if (timerRef.current) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => fnRef.current(...args), delay);\n },\n [delay]\n );\n}\n"],"mappings":"+DAGA,SAAgB,EACdA,EACAC,EACkC,CAElC,IADM,EAAQ,EAAO,EAAG,CAClB,EAAW,EAA6C,KAAK,CAMnE,MAJA,GAAU,IAAM,CACd,EAAM,QAAU,CACjB,EAAC,CAEK,EACL,CAAC,GAAG,IAAwB,CAE1B,AADI,EAAS,SAAS,aAAa,EAAS,QAAQ,CACpD,EAAS,QAAU,WAAW,IAAM,EAAM,QAAQ,GAAG,EAAK,CAAE,EAAM,AACnE,EACD,CAAC,CAAM,EACR,AACF"}
{"version":3,"file":"index.js","names":["DEFAULT_LOCALES: Record<Lang, string>","s: string","base: string","path: string","value: string","url: string","version: string | undefined","external: boolean","site: SiteConfig","orgId: string","editorId: string","ctx: JsonLdContext","websiteId: string","input: PageSeoInput","work: JsonLdWork","itemReviewed: JsonLdWork","rating: JsonLdRating | undefined","items: { name: string; path: string }[]","event: JsonLdEvent","pageNodes: Thing[]","baseNodes: Thing[]","lang: Lang","link: SeoLink[]","meta: SeoMeta[]","fn: T","delay: number","site: SiteConfig","meta: HeadMeta[]","links: HeadLink[]","scripts: HeadScript[]"],"sources":["../../src/metadata/core.ts","../../src/react/index.ts"],"sourcesContent":["import type {\n Article,\n Book,\n BreadcrumbList,\n Event,\n Graph,\n Movie,\n MusicAlbum,\n MusicEvent,\n MusicRecording,\n Organization,\n Person,\n Review,\n Thing,\n WebSite,\n} from 'schema-dts';\n\nexport type Lang = 'ko' | 'en';\n\nexport type SeoImage = {\n /** Absolute URL or path starting with `/`. */\n path: string;\n width?: number;\n height?: number;\n type?: string;\n alt?: string;\n};\n\nexport type ArticleMeta = {\n /** ISO 8601 timestamp. */\n publishedTime: string;\n /** ISO 8601 timestamp. */\n modifiedTime?: string;\n author?: string;\n section?: string;\n tags?: string[];\n};\n\n/**\n * apps/web `WorkType` 와 1:1. 도메인 타입을 패키지로 끌어오지 않으려고 문자열 리터럴로 받는다.\n * `buildJsonLd` 가 schema.org `@type` 으로 매핑한다 (albums→MusicAlbum 등).\n */\nexport type CreativeWorkKind =\n | 'albums'\n | 'tracks'\n | 'concerts'\n | 'films'\n | 'books'\n | 'events';\n\n/** 작품 노드 입력 — Review.itemReviewed · CreativeWork 페이지 공용. */\nexport type JsonLdWork = {\n kind: CreativeWorkKind;\n name: string;\n /** 아티스트 / 저자 / 감독 — kind 에 따라 byArtist · author · director · performer 로 매핑. */\n by?: string;\n /** 발매·개최 연도. `String()` 으로 datePublished/startDate 에 들어간다. */\n year?: number | string;\n /** Path(`/...`) 또는 절대 URL. */\n image?: string;\n /** 작품 페이지 path(`/...`) 또는 절대 URL. */\n url?: string;\n /** 외부 출처 — bandcamp · spotify · imdb 등. */\n sameAs?: string[];\n};\n\nexport type JsonLdRating = { value: number; best?: number; worst?: number };\n\nexport type JsonLdEventOffer = {\n price: number;\n currency: string;\n /** Path(`/...`) 또는 절대 URL. */\n url: string;\n /** ISO 8601 — 티켓 오픈 시각. */\n validFrom: string;\n name?: string;\n};\n\n/**\n * 페이지 본체가 곧 그 공연인 경우의 풀 Event 노드 입력. 얕은 `JsonLdWork('concerts')`(참조용)와\n * 달리 venue(geo)·offers·organizer 까지 채워 Google Event 리치 결과 자격을 충족한다.\n */\nexport type JsonLdEvent = {\n name: string;\n /** 이벤트 페이지 path(`/...`) 또는 절대 URL. */\n url: string;\n /** ISO 8601 — 연도-only 금지(리치 결과 유효성 실패). */\n startDate: string;\n /** ISO 8601 */\n endDate?: string;\n venue: {\n name: string;\n address: string;\n latitude: number;\n longitude: number;\n };\n /** Path(`/...`) 또는 절대 URL 목록. */\n images?: string[];\n description?: string;\n offers?: JsonLdEventOffer[];\n /** 주최자명. 미지정 시 `venue.name` 으로 대체. */\n organizer?: string;\n};\n\n/**\n * 페이지에 emit 할 구조화 데이터 디스크립터. `buildJsonLd` 가 단일 `@graph` 로 합쳐 직렬화한다.\n * publisher(Organization) · editor(Person) base 노드는 매 페이지 inline 되어 `@id` 참조를 해소한다.\n */\nexport type JsonLd =\n | { type: 'WebSite' }\n /** `input.article` 에서 파생 — article 없으면 무시. */\n | { type: 'Article' }\n | { type: 'Review'; itemReviewed: JsonLdWork; rating?: JsonLdRating }\n | { type: 'CreativeWork'; item: JsonLdWork }\n /** 페이지 본체가 곧 그 공연 — venue/offers 까지 갖춘 풀 MusicEvent. */\n | { type: 'EventPage'; event: JsonLdEvent }\n | { type: 'Breadcrumb'; items: { name: string; path: string }[] };\n\nexport type PageSeoInput = {\n title: string;\n description: string;\n path: string;\n lang?: Lang;\n /** Alternate language versions for this page. Emits hreflang link tags. */\n alternates?: { lang: Lang; path: string }[];\n /** When set, og:type switches to `article` and article:* tags are emitted. */\n article?: ArticleMeta;\n /** Override the default OG/Twitter share image for this page. */\n image?: SeoImage;\n /** Per-page keywords. Merged with `SiteConfig.keywords` into a `keywords` meta. */\n keywords?: string[];\n /** Structured data (JSON-LD) to emit for this page. Build-time consumers only. */\n jsonLd?: JsonLd[];\n /**\n * Per-page App Links deep-link URLs. App identity(store id/package/name)는 `SiteConfig.appLinks`\n * 에서 오고, 여기선 이 페이지 콘텐츠로 가는 딥링크 URL 만 준다. `SiteConfig.appLinks` 가 없으면 무시.\n */\n appLinks?: { iosUrl?: string; androidUrl?: string };\n};\n\nexport type SiteConfig = {\n /** Display name appended to every page title. */\n name: string;\n /** Origin used to resolve absolute URLs. Trailing slash is normalized. */\n baseUrl: string;\n /** Default OG/Twitter image used when a page does not override it. */\n defaultImage?: SeoImage;\n /** Path emitted as `og:logo` (Schema.org / LinkedIn extension). */\n logoPath?: string;\n /** Override locale strings for og:locale. Defaults: ko → ko_KR, en → en_US. */\n locales?: Partial<Record<Lang, string>>;\n /** Site-wide keywords merged into every page's `keywords` meta. */\n keywords?: string[];\n /**\n * Cache-busting token appended as `?v=<assetVersion>` to *own-domain* OG/Twitter\n * image URLs (paths, not external `http` URLs). Bump it when an image is replaced\n * in place under the same filename so social scrapers (Threads/Slack/iMessage 등)\n * treat it as a new URL instead of serving their stale cache.\n */\n assetVersion?: string;\n /** Publishing organization — emitted as the `Organization` JSON-LD node. */\n publisher?: {\n name: string;\n url?: string;\n logoPath?: string;\n sameAs?: string[];\n };\n /** Editor — emitted as the `Person` JSON-LD node, referenced as article author. */\n editor?: { name: string; url?: string; sameAs?: string[] };\n /**\n * 네이티브 앱 아이덴티티 — 존재 시 `al:ios:*` / `al:android:*` (Facebook App Links) 메타를 emit.\n * 페이지별 딥링크 URL 은 `PageSeoInput.appLinks` 에서 주입한다. Twitter `app` 카드는 쓰지 않는다\n * (이미지 카드 우선 — 포스터 미리보기 CTR 보존). 딥링크는 카드 종류와 무관하게 동작한다.\n */\n appLinks?: {\n ios?: { appStoreId: string; appName: string };\n android?: { packageName: string; appName?: string };\n };\n};\n\nexport type SeoMeta = { name?: string; property?: string; content: string };\nexport type SeoLink = { rel: string; href: string; hreflang?: string };\nexport type SeoScript = { type: 'application/ld+json'; innerHTML: string };\n\nexport type SeoTags = {\n title: string;\n htmlLang: Lang;\n meta: SeoMeta[];\n link: SeoLink[];\n /** JSON-LD `<script>` tags. Empty unless `input.jsonLd` is set. */\n script: SeoScript[];\n};\n\nconst DEFAULT_LOCALES: Record<Lang, string> = {\n ko: 'ko_KR',\n en: 'en_US',\n};\n\nconst SCHEMA_CONTEXT = 'https://schema.org';\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction joinUrl(base: string, path: string): string {\n const b = base.replace(/\\/$/, '');\n const p = path.startsWith('/') ? path : `/${path}`;\n return `${b}${p}`;\n}\n\n/** Resolve a value that may be a path(`/...`) or an already-absolute URL. */\nfunction absoluteUrl(base: string, value: string): string {\n return value.startsWith('http') ? value : joinUrl(base, value);\n}\n\n/**\n * Append `?v=<version>` (or `&v=`) for cache-busting. No-op when version is empty.\n * `external` skips the param entirely — we don't control third-party CDN URLs\n * (signed poster URLs could break) and only want to bust our own re-uploaded assets.\n */\nfunction withAssetVersion(\n url: string,\n version: string | undefined,\n external: boolean\n): string {\n if (!version || external) return url;\n return `${url}${url.includes('?') ? '&' : '?'}v=${encodeURIComponent(version)}`;\n}\n\n/**\n * Absolute *page* (route) URL.\n *\n * 과거 Next.js (`trailingSlash: true` + directory-index 호스팅) 시절엔 모든 page URL 에\n * `/` 를 강제로 붙여 canonical 과 sitemap 을 호스트 redirect 와 정합시켰지만, TSS +\n * Cloudflare Workers SSR 로 이관 후엔 라우트가 slash 없이 서빙된다 (`/event/foo` 는 200,\n * `/event/foo/` 는 의도된 canonical 이 아님). 따라서 `absoluteUrl` 결과를 그대로 둔다.\n */\nfunction pageUrl(base: string, value: string): string {\n return absoluteUrl(base, value);\n}\n\ntype JsonLdContext = {\n url: string;\n fullTitle: string;\n imageUrl?: string;\n lang: Lang;\n};\n\nfunction orgNode(site: SiteConfig, orgId: string): Organization | null {\n const p = site.publisher;\n if (!p) return null;\n return {\n '@type': 'Organization',\n '@id': orgId,\n name: p.name,\n url: p.url ?? site.baseUrl.replace(/\\/$/, ''),\n logo: p.logoPath ? joinUrl(site.baseUrl, p.logoPath) : undefined,\n sameAs: p.sameAs,\n } satisfies Organization;\n}\n\nfunction personNode(site: SiteConfig, editorId: string): Person | null {\n const e = site.editor;\n if (!e) return null;\n return {\n '@type': 'Person',\n '@id': editorId,\n name: e.name,\n url: e.url,\n sameAs: e.sameAs,\n } satisfies Person;\n}\n\nfunction websiteNode(\n site: SiteConfig,\n ctx: JsonLdContext,\n websiteId: string,\n orgId: string\n): WebSite {\n return {\n '@type': 'WebSite',\n '@id': websiteId,\n name: site.name,\n url: site.baseUrl.replace(/\\/$/, ''),\n inLanguage: ctx.lang,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n } satisfies WebSite;\n}\n\nfunction articleNode(\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext,\n orgId: string,\n editorId: string\n): Article | null {\n const a = input.article;\n if (!a) return null;\n return {\n '@type': 'BlogPosting',\n headline: input.title,\n description: input.description,\n url: ctx.url,\n mainEntityOfPage: ctx.url,\n inLanguage: ctx.lang,\n datePublished: a.publishedTime,\n dateModified: a.modifiedTime,\n image: ctx.imageUrl,\n articleSection: a.section,\n keywords: a.tags,\n author: site.editor\n ? { '@id': editorId }\n : a.author\n ? { '@type': 'Person', name: a.author }\n : undefined,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n } satisfies Article;\n}\n\n/**\n * `JsonLdWork` → schema.org 작품 노드. kind 에 따라 @type 과 creator 프로퍼티를 매핑한다.\n * creator(`by`)는 문자열이 아니라 `MusicGroup`/`Person` 객체로 감싸야 schema-dts 가 받는다.\n */\nfunction workNode(work: JsonLdWork, base: string): Thing {\n const name = work.name;\n const image = work.image ? absoluteUrl(base, work.image) : undefined;\n const url = work.url ? pageUrl(base, work.url) : undefined;\n const sameAs = work.sameAs?.length ? work.sameAs : undefined;\n const year = work.year != null ? String(work.year) : undefined;\n const musicGroup = work.by\n ? ({ '@type': 'MusicGroup', name: work.by } as const)\n : undefined;\n const person = work.by\n ? ({ '@type': 'Person', name: work.by } as const)\n : undefined;\n\n switch (work.kind) {\n case 'albums':\n return {\n '@type': 'MusicAlbum',\n name,\n byArtist: musicGroup,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies MusicAlbum;\n case 'tracks':\n return {\n '@type': 'MusicRecording',\n name,\n byArtist: musicGroup,\n image,\n url,\n sameAs,\n } satisfies MusicRecording;\n case 'concerts':\n return {\n '@type': 'MusicEvent',\n name,\n performer: musicGroup,\n image,\n url,\n sameAs,\n startDate: year,\n } satisfies MusicEvent;\n case 'events':\n return {\n '@type': 'Event',\n name,\n performer: musicGroup,\n image,\n url,\n sameAs,\n startDate: year,\n } satisfies Event;\n case 'films':\n return {\n '@type': 'Movie',\n name,\n director: person,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies Movie;\n case 'books':\n return {\n '@type': 'Book',\n name,\n author: person,\n image,\n url,\n sameAs,\n datePublished: year,\n } satisfies Book;\n }\n}\n\nfunction reviewNode(\n itemReviewed: JsonLdWork,\n rating: JsonLdRating | undefined,\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext,\n base: string,\n orgId: string,\n editorId: string\n): Review {\n const a = input.article;\n return {\n '@type': 'Review',\n name: ctx.fullTitle,\n reviewBody: input.description,\n url: ctx.url,\n inLanguage: ctx.lang,\n datePublished: a?.publishedTime,\n author: site.editor\n ? { '@id': editorId }\n : a?.author\n ? { '@type': 'Person', name: a.author }\n : undefined,\n publisher: site.publisher ? { '@id': orgId } : undefined,\n itemReviewed: workNode(itemReviewed, base),\n reviewRating: rating\n ? {\n '@type': 'Rating',\n ratingValue: rating.value,\n bestRating: rating.best,\n worstRating: rating.worst,\n }\n : undefined,\n } satisfies Review;\n}\n\nfunction breadcrumbNode(\n items: { name: string; path: string }[],\n base: string\n): BreadcrumbList {\n return {\n '@type': 'BreadcrumbList',\n itemListElement: items.map((it, i) => ({\n '@type': 'ListItem',\n position: i + 1,\n name: it.name,\n item: pageUrl(base, it.path),\n })),\n } satisfies BreadcrumbList;\n}\n\n/**\n * `JsonLdEvent` → 풀 `MusicEvent` 노드. venue(Place+GeoCoordinates) · offers(Offer) · organizer\n * 까지 채워 Google Event 리치 결과 자격을 충족한다. `@graph` 노드라 `@context` 는 붙이지 않는다.\n */\nfunction eventNode(event: JsonLdEvent, base: string): MusicEvent {\n return {\n '@type': 'MusicEvent',\n name: event.name,\n url: pageUrl(base, event.url),\n startDate: event.startDate,\n endDate: event.endDate,\n eventAttendanceMode: 'https://schema.org/OfflineEventAttendanceMode',\n eventStatus: 'https://schema.org/EventScheduled',\n location: {\n '@type': 'Place',\n name: event.venue.name,\n address: event.venue.address,\n geo: {\n '@type': 'GeoCoordinates',\n latitude: event.venue.latitude,\n longitude: event.venue.longitude,\n },\n },\n image: event.images?.length\n ? event.images.map((img) => absoluteUrl(base, img))\n : undefined,\n description: event.description,\n offers: event.offers?.length\n ? event.offers.map((offer) => ({\n '@type': 'Offer',\n availability: 'https://schema.org/InStock',\n price: offer.price,\n priceCurrency: offer.currency,\n url: absoluteUrl(base, offer.url),\n validFrom: offer.validFrom,\n name: offer.name,\n }))\n : undefined,\n organizer: {\n '@type': 'Organization',\n name: event.organizer ?? event.venue.name,\n },\n } satisfies MusicEvent;\n}\n\n/**\n * 페이지 디스크립터 → 단일 `@graph` JSON-LD `<script>`.\n * publisher/editor base 노드를 항상 prepend 해 페이지 단위로 `@id` 참조가 해소되게 한다.\n */\nfunction buildJsonLd(\n input: PageSeoInput,\n site: SiteConfig,\n ctx: JsonLdContext\n): SeoScript[] {\n const descriptors = input.jsonLd ?? [];\n if (descriptors.length === 0) return [];\n\n const base = site.baseUrl.replace(/\\/$/, '');\n const orgId = `${base}/#org`;\n const editorId = `${base}/#editor`;\n const websiteId = `${base}/#website`;\n\n const pageNodes: Thing[] = [];\n for (const d of descriptors) {\n switch (d.type) {\n case 'WebSite':\n pageNodes.push(websiteNode(site, ctx, websiteId, orgId));\n break;\n case 'Article': {\n const node = articleNode(input, site, ctx, orgId, editorId);\n if (node) pageNodes.push(node);\n break;\n }\n case 'Review':\n pageNodes.push(\n reviewNode(\n d.itemReviewed,\n d.rating,\n input,\n site,\n ctx,\n base,\n orgId,\n editorId\n )\n );\n break;\n case 'CreativeWork':\n pageNodes.push(workNode(d.item, base));\n break;\n case 'EventPage':\n pageNodes.push(eventNode(d.event, base));\n break;\n case 'Breadcrumb':\n pageNodes.push(breadcrumbNode(d.items, base));\n break;\n }\n }\n if (pageNodes.length === 0) return [];\n\n const baseNodes: Thing[] = [];\n const org = orgNode(site, orgId);\n if (org) baseNodes.push(org);\n const person = personNode(site, editorId);\n if (person) baseNodes.push(person);\n\n const graph = {\n '@context': SCHEMA_CONTEXT,\n '@graph': [...baseNodes, ...pageNodes],\n } satisfies Graph;\n\n return [{ type: 'application/ld+json', innerHTML: JSON.stringify(graph) }];\n}\n\nexport function buildSeoTags(input: PageSeoInput, site: SiteConfig): SeoTags {\n const lang: Lang = input.lang ?? 'ko';\n // 라우트 측에서 이미 브랜드 suffix(`… | COLDSURF 공연 정보`, `… — COLDSURF`, `Engineering — COLDSURF`)를\n // 직접 박은 경우, 여기서 다시 `| ${site.name}` 을 더하면 `... | COLDSURF 공연 정보 | COLDSURF` 처럼 두 번 노출된다.\n // title 안에 site.name 토큰이 이미 등장하면 그대로 둔다 — 라우트의 의도를 보존하면서 중복만 제거.\n const titleHasBrand = new RegExp(`\\\\b${escapeRegExp(site.name)}\\\\b`).test(\n input.title\n );\n const fullTitle = titleHasBrand\n ? input.title\n : `${input.title} | ${site.name}`;\n const url = pageUrl(site.baseUrl, input.path);\n const alternates = input.alternates ?? [];\n const locales = { ...DEFAULT_LOCALES, ...site.locales };\n\n const link: SeoLink[] = [{ rel: 'canonical', href: url }];\n if (alternates.length > 0) {\n link.push({ rel: 'alternate', hreflang: lang, href: url });\n for (const alt of alternates) {\n link.push({\n rel: 'alternate',\n hreflang: alt.lang,\n href: pageUrl(site.baseUrl, alt.path),\n });\n }\n // x-default points at the primary (Korean) language when present.\n const xDefault =\n alternates.find((a) => a.lang === 'ko')?.path ?? input.path;\n link.push({\n rel: 'alternate',\n hreflang: 'x-default',\n href: pageUrl(site.baseUrl, xDefault),\n });\n }\n\n const meta: SeoMeta[] = [\n { name: 'description', content: input.description },\n { name: 'robots', content: 'index, follow' },\n { property: 'og:title', content: fullTitle },\n { property: 'og:description', content: input.description },\n { property: 'og:type', content: input.article ? 'article' : 'website' },\n { property: 'og:url', content: url },\n { property: 'og:site_name', content: site.name },\n { property: 'og:locale', content: locales[lang] },\n ];\n\n const keywords = [\n ...new Set([...(input.keywords ?? []), ...(site.keywords ?? [])]),\n ];\n if (keywords.length > 0) {\n meta.push({ name: 'keywords', content: keywords.join(', ') });\n }\n\n const image = input.image ?? site.defaultImage;\n // SeoImage.path 는 doc 상 \"절대 URL 또는 `/` 시작 path\" — venue 포스터처럼 외부 CDN 풀\n // URL 도 받기 위해 absoluteUrl(http 접두면 그대로) 로 통과시킨다.\n // 자체 도메인 자산만 `?v=` cache-bust — 외부 http URL 은 건드리지 않는다.\n const imageUrl = image\n ? withAssetVersion(\n absoluteUrl(site.baseUrl, image.path),\n site.assetVersion,\n image.path.startsWith('http')\n )\n : undefined;\n if (image && imageUrl) {\n meta.push({ property: 'og:image', content: imageUrl });\n if (image.type)\n meta.push({ property: 'og:image:type', content: image.type });\n if (image.width)\n meta.push({ property: 'og:image:width', content: String(image.width) });\n if (image.height)\n meta.push({ property: 'og:image:height', content: String(image.height) });\n if (image.alt) meta.push({ property: 'og:image:alt', content: image.alt });\n meta.push({ name: 'twitter:image', content: imageUrl });\n if (image.alt) meta.push({ name: 'twitter:image:alt', content: image.alt });\n }\n\n if (site.logoPath) {\n meta.push({\n property: 'og:logo',\n content: joinUrl(site.baseUrl, site.logoPath),\n });\n }\n\n // summary_large_image (1.91:1) 에서 잘리지 않을 만큼 가로로 긴 이미지일 때만 큰 카드로 노출.\n // 정사각형 이하 비율은 summary (작은 카드)로 떨어뜨려 안전하게 보여준다.\n const isWideImage =\n !!image &&\n !!image.width &&\n !!image.height &&\n image.width >= image.height * 1.5;\n const twitterCard = isWideImage ? 'summary_large_image' : 'summary';\n meta.push(\n { name: 'twitter:card', content: twitterCard },\n { name: 'twitter:title', content: fullTitle },\n { name: 'twitter:description', content: input.description }\n );\n\n for (const alt of alternates) {\n if (alt.lang !== lang) {\n meta.push({\n property: 'og:locale:alternate',\n content: locales[alt.lang],\n });\n }\n }\n\n if (input.article) {\n meta.push({\n property: 'article:published_time',\n content: input.article.publishedTime,\n });\n if (input.article.modifiedTime) {\n meta.push({\n property: 'article:modified_time',\n content: input.article.modifiedTime,\n });\n }\n if (input.article.author) {\n meta.push({ property: 'article:author', content: input.article.author });\n }\n if (input.article.section) {\n meta.push({\n property: 'article:section',\n content: input.article.section,\n });\n }\n for (const tag of input.article.tags ?? []) {\n meta.push({ property: 'article:tag', content: tag });\n }\n }\n\n // App Links (al:*) — 앱 아이덴티티는 site, 딥링크 URL 은 페이지에서. Twitter app 카드는 의도적으로\n // 쓰지 않는다(이미지 카드 우선) — al:* 는 카드 종류와 무관하게 네이티브 앱 오픈을 동작시킨다.\n if (site.appLinks?.ios) {\n meta.push({\n property: 'al:ios:app_store_id',\n content: site.appLinks.ios.appStoreId,\n });\n meta.push({\n property: 'al:ios:app_name',\n content: site.appLinks.ios.appName,\n });\n if (input.appLinks?.iosUrl) {\n meta.push({ property: 'al:ios:url', content: input.appLinks.iosUrl });\n }\n }\n if (site.appLinks?.android) {\n meta.push({\n property: 'al:android:package',\n content: site.appLinks.android.packageName,\n });\n if (site.appLinks.android.appName) {\n meta.push({\n property: 'al:android:app_name',\n content: site.appLinks.android.appName,\n });\n }\n if (input.appLinks?.androidUrl) {\n meta.push({\n property: 'al:android:url',\n content: input.appLinks.androidUrl,\n });\n }\n }\n\n const script = buildJsonLd(input, site, { url, fullTitle, imageUrl, lang });\n\n return { title: fullTitle, htmlLang: lang, meta, link, script };\n}\n\n/**\n * `SiteConfig` 를 한 번 바인딩해 페이지 입력만 받는 `buildTags(input)` 를 만든다. 구\n * `NextMetadataGenerator` 의 \"생성자 1회 주입\" 사용감을 stateful 클래스 없이 순수 함수로 재현한다.\n *\n * const seo = createSeo(siteConfig)\n * seo.buildTags({ title, description, path, jsonLd: [...] })\n */\nexport function createSeo(site: SiteConfig) {\n return {\n buildTags: (input: PageSeoInput): SeoTags => buildSeoTags(input, site),\n };\n}\n","import { useCallback, useEffect, useRef } from 'react';\nimport {\n type PageSeoInput,\n type SeoLink,\n type SeoMeta,\n type SeoScript,\n type SiteConfig,\n buildSeoTags,\n} from '../metadata/core';\n\n// biome-ignore lint/suspicious/noExplicitAny: generic callback signature\nexport function useDebouncedCallback<T extends (...args: any[]) => void>(\n fn: T,\n delay: number\n): (...args: Parameters<T>) => void {\n const fnRef = useRef(fn);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n fnRef.current = fn;\n });\n\n return useCallback(\n (...args: Parameters<T>) => {\n if (timerRef.current) clearTimeout(timerRef.current);\n timerRef.current = setTimeout(() => fnRef.current(...args), delay);\n },\n [delay]\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// SEO head adapter\n//\n// `buildSeoTags` (framework-agnostic engine, ./metadata) 의 `SeoTags` 출력을, head()/meta API 가\n// 평범한 태그 디스크립터를 받는 React 라우터(예: TanStack Router)의 `{ meta, links, scripts }` 모양으로\n// 매핑한다. React import 에 의존하지 않는 순수 변환 — 어떤 head-descriptor 라우터에도 쓸 수 있다.\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type HeadMeta =\n | { title: string }\n | { charSet: string }\n | { name: string; content: string }\n | { property: string; content: string }\n | { httpEquiv: string; content: string };\n\nexport type HeadLink = {\n rel: string;\n href: string;\n hreflang?: string;\n type?: string;\n sizes?: string;\n};\n\nexport type HeadScript = {\n children?: string;\n src?: string;\n type?: string;\n async?: boolean;\n};\n\nexport type SeoHead = {\n meta: HeadMeta[];\n links: HeadLink[];\n scripts: HeadScript[];\n};\n\n/** `PageSeoInput` + `robots` override. 미지정 시 엔진 기본값 `'index, follow'`. */\nexport type SeoHeadInput = PageSeoInput & { robots?: string };\n\n/**\n * `SiteConfig` 를 한 번 바인딩해, 라우트에서 단일 declarative 입력으로 호출하는 `seoHead(input)` 를 만든다.\n *\n * // app 진입 1회\n * export const seoHead = createSeoHead({ ...SITE, baseUrl })\n *\n * // 라우트\n * head: ({ match }) => seoHead({ path: '/about', lang: match.context.lang, title, description })\n * head: () => seoHead({ path: '/resume', lang: 'ko', ...copy, robots: 'noindex, nofollow' })\n *\n * 반환값(`{ meta, links, scripts }`)을 라우터가 `<head>` 로 직렬화한다.\n */\nexport function createSeoHead(\n site: SiteConfig\n): (input: SeoHeadInput) => SeoHead {\n return (input) => {\n const { robots, ...page } = input;\n const tags = buildSeoTags(page, site);\n\n const meta: HeadMeta[] = [{ title: tags.title }];\n for (const m of tags.meta as SeoMeta[]) {\n if (m.name) {\n // robots override — noindex 라우트만 명시.\n const content = m.name === 'robots' && robots ? robots : m.content;\n meta.push({ name: m.name, content });\n } else if (m.property) {\n meta.push({ property: m.property, content: m.content });\n }\n // name 도 property 도 없는 SeoMeta 는 spec 위반 — skip.\n }\n\n const links: HeadLink[] = (tags.link as SeoLink[]).map((l) => ({\n rel: l.rel,\n href: l.href,\n ...(l.hreflang ? { hreflang: l.hreflang } : {}),\n }));\n\n const scripts: HeadScript[] = (tags.script as SeoScript[]).map((s) => ({\n type: s.type,\n children: s.innerHTML,\n }));\n\n return { meta, links, scripts };\n };\n}\n\n// app code 가 `seoHead` 입력을 조립할 때 필요한 metadata 타입 재노출 — `@coldsurf/shared-utils/react`\n// 한 곳에서 끝나게.\nexport type {\n ArticleMeta,\n JsonLd,\n JsonLdEvent,\n JsonLdEventOffer,\n JsonLdWork,\n Lang,\n PageSeoInput,\n SeoImage,\n SiteConfig,\n} from '../metadata/core';\n"],"mappings":"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"}

@@ -8,3 +8,3 @@ {

},
"version": "1.3.1",
"version": "1.4.0",
"license": "MIT",

@@ -11,0 +11,0 @@ "bugs": {

@@ -44,3 +44,3 @@ # @coldsurfers/shared-utils

- `decodeJwt` (`utils.jwt`) uses `jwt-decode` at runtime.
- `NextMetadataGenerator` (`utils.metadata`) uses `schema-dts` types for JSON-LD typing.
- `buildSeoTags` / `createSeo` (`metadata`) uses `schema-dts` types for JSON-LD typing.

@@ -57,3 +57,3 @@ ## What to install by use case

```
- You use `NextMetadataGenerator` / `generateLdJson` with `schema-dts` types:
- You use `buildSeoTags` / `createSeo` / `createSeoHead` with `schema-dts` types:
```bash

@@ -60,0 +60,0 @@ pnpm add @coldsurfers/shared-utils zod schema-dts