EDITORCLASSBLOGLOGINimg default descriptionimg default descriptionimg default description표현한다는 것의무한한 가능성,새로운 형태로
담아내다.
img default descriptionimg default descriptionimg default descriptionimg default descriptionimg default descriptionimg default descriptionimg default description새로운 형태의 콘텐츠Opkle은 코드 없이 웹을 마음껏 만들고, 누구나 자기 화면을 그릴 수 있도록 에디터를 만들고, 그 결과물을 어디서나 즐길 수 있게 돕는 팀입니다.텍스트와 화면과의 조화를 통해, 웹을 짓는다는 것이 그저 단순한 코딩이 아닌, 상상을 펼치고 감각을 깨우는 과정이 될 수 있도록 좋은 도구를 만들어 냅니다.
옵클 에디터 개발기: 표 이미지를 만들기 위한 스프레드시트dev17옵클 에디터 개발기: 같은 데이터를 표와 보드, 캘린더로dev18옵클 에디터 개발기: 책을 웹에서 그대로 읽게 하는 퍼블리셔dev19옵클 에디터 개발기: 편집 화면을 그대로 웹에 게시하기dev20옵클 에디터 개발기: EPUB 에디터가 모든 작업을 연결하는 방식dev21910111213...18 옵클 에디터 개발기: 같은 데이터를 표와 보드, 캘린더로스프레드시트 편집이 완성된 뒤에는 그 데이터를 어떤 결과로 보여줄 것인지가 남았습니다. 타입이 있는 열과 안정적인 레코드가 생기면 같은 relation을 표로만 볼 이유가 없습니다. 범주형 속성은 칸반의 partition이 될 수 있고 날짜 interval은 캘린더의 시간축이 될 수 있습니다. 화면은 데이터가 아니라 데이터에 적용한 질의였습니다.그리드, 칸반, 캘린더를 서로 다른 문서로 만들지 않았습니다. 하나의 base relation에 selection, grouping, ordering과 projection을 적용해 서로 다른 view를 만들었습니다. 어느 화면에서 값을 바꾸더라도 변경은 base tuple로 돌아가고 다른 view는 같은 revision을 다시 해석합니다. 데이터 복제보다 관계대수의 닫힌 연산으로 보는 편이 정확했습니다.출력도 화면을 캡처하는 방식으로 만들지 않았습니다. view의 의미 구조에서 별도의 scene graph를 만들고 벡터와 래스터 출력은 그 graph를 표본화한 결과가 됩니다. 화면 크기와 테마가 달라져도 원본 relation은 그대로이고 layout constraint만 바뀝니다. 표 에디터가 데이터 입력 도구에서 출판용 시각 자료 제작기로 확장된 과정입니다.Relational projection하나의 relation T에서 필요한 열만 고르는 projection `π`, 조건에 맞는 행을 고르는 selection `σ`, 범주별로 묶는 grouping과 순서를 정하는 `τ`를 합치면 view가 됩니다. 그리드는 T에 가까운 projection이고 칸반과 캘린더는 더 강한 변환을 적용한 결과입니다. view가 base tuple을 복제하지 않으면 여러 화면의 정합성을 따로 맞출 필요가 없습니다.다만 view가 완전히 stateless한 함수인 것은 아닙니다. 어떤 attribute를 축으로 사용할지, 어떤 속성을 표시할지와 사용자가 정한 순서는 query parameter입니다. 이를 base data와 구분된 declarative query로 보존하면 같은 relation에서 동일한 view를 다시 계산할 수 있습니다. 화면 DOM은 query result의 일시적인 materialization일 뿐입니다.materialized view는 읽기에는 빠르지만 base가 바뀔 때 갱신이 필요합니다. 모든 변경마다 전체 view를 다시 계산할 수도 있고 delta만 반영하는 incremental view maintenance를 사용할 수도 있습니다. insert와 delete가 grouping count와 정렬 순서에 미치는 영향을 algebraically 계산하면 큰 relation에서도 변경된 부분만 갱신할 수 있습니다.중요한 것은 어느 화면에서 시작한 편집도 base relation의 transaction으로 번역된다는 점이었습니다. 카드 이동과 일정 변경을 별도 상태로 저장하면 그리드와 충돌하지만, base tuple의 attribute update로 환원하면 모든 view가 같은 revision을 읽습니다. one table, many views는 UI 구성보다 데이터의 단일성에 대한 원칙이었습니다.Identity와 permutation행과 열의 위치는 permutation에 따라 바뀌지만 identity는 유지되어야 합니다. 순서 n개의 항목은 대칭군 `S_n`의 permutation으로 재배열할 수 있고, view가 좌표 index를 identity로 사용하면 permutation 한 번에 모든 참조의 의미가 달라집니다. 객체와 그 현재 위치를 분리하는 것이 다중 view의 기초였습니다.relation의 tuple은 surrogate identity를 가질 수 있고 attribute도 표시 순서와 독립된 symbol로 다룰 수 있습니다. 정렬은 identity 집합 위에 정의된 total order일 뿐입니다. 새 항목을 중간에 넣는 일은 order relation을 바꾸지만 기존 identity의 동등관계를 바꾸지 않습니다. 데이터베이스 key와 화면 좌표가 다른 이유입니다.사용자 순서는 단순한 정렬 결과보다 강한 의미를 가질 수 있습니다. 값이 비어 있어도 사용자가 만든 레코드는 존재해야 하고, 특정 view의 순서는 base value만으로 다시 계산되지 않을 수 있습니다. 존재 집합과 order relation을 분리하면 빈 레코드와 사용자 지정 순서를 함께 설명할 수 있습니다.undo와 이식에서도 보존할 것은 숫자 좌표가 아니라 이 identity와 relation입니다. permutation 전후의 객체 대응이 유지되면 view가 가리키는 attribute와 tuple도 안정적입니다. 화면 좌표를 영구 식별자로 사용하지 않는다는 원칙은 표뿐 아니라 모든 편집 시스템에서 같은 의미를 가졌습니다.Quotient와 order칸반은 범주형 함수 `g:R->K`가 행 집합 R을 lane 집합 K로 나누는 projection입니다. 같은 g값을 가진 행들을 동치로 보면 `r1 ~ r2 iff g(r1)=g(r2)`가 되고 lane은 quotient set `R/~`의 동치류가 됩니다. 어느 범주에도 속하지 않는 값도 하나의 명시적인 class로 다뤄야 partition이 전체 R을 덮습니다.다중 범주는 equivalence relation보다 incidence relation에 가깝습니다. 한 카드가 여러 lane에 동시에 속할 수 있으므로 `M ⊆ R × K`인 이분 graph가 됩니다. 카드를 옮긴다는 것은 edge를 추가하거나 제거하는 연산이고, 다른 범주 edge를 보존할지 모두 교체할지는 명령의 의미에 따라 달라집니다.lane 안의 카드 순서는 값에서 자동으로 따라오는 경우도 있지만 사용자가 정한 total order일 수도 있습니다. 순서를 정수 index로 저장하면 중간 삽입 때 뒤쪽 항목을 모두 바꿔야 합니다. 두 이웃 사이에 order key를 배정하는 fractional indexing이나 linked order를 생각할 수 있지만, key 공간의 밀도와 재균형 비용이 생깁니다. 순서 자체도 하나의 자료구조였습니다.drag and drop은 화면 위치를 이 relation update로 번역합니다. pointer의 연속 좌표에서 가장 가까운 order interval을 찾고, lane membership과 order를 하나의 transaction으로 갱신해야 합니다. membership만 먼저 바뀌면 카드가 잠시 사라지고 order만 바뀌면 다른 lane의 순서를 오염시킵니다. UI gesture 하나가 quotient와 order relation을 동시에 바꾸는 연산이었습니다.카드에 보이는 속성은 base tuple의 projection입니다. 칸반을 별도 문서 모델로 만들 필요가 없습니다. 제목과 상태, 날짜를 수정하는 모든 동작이 원래 tuple update로 환원되면 그리드와 캘린더도 같은 revision에서 즉시 같은 값을 읽습니다. 칸반은 데이터를 복제한 보드가 아니라 relation에 대한 구조화된 질의가 되었습니다.Interval algebra캘린더에서 한 이벤트는 점이 아니라 시간축 위의 interval입니다. 시작 s와 종료 e가 있을 때 `[s,e)`처럼 반열린 구간으로 정의하면 하루짜리 일정과 다음 날 시작 일정이 경계에서 겹치지 않습니다. 날짜만 있는 값과 시간대가 있는 instant를 같은 숫자로 다루면 daylight saving과 locale에서 의미가 흔들릴 수 있으므로 시간 domain을 먼저 정해야 합니다.두 interval의 관계는 단순한 겹침 여부보다 풍부합니다. Allen의 interval algebra에는 before, meets, overlaps, during, starts와 finishes 같은 기본 관계가 있습니다. 월간 화면에서 이벤트를 lane에 배치하려면 서로 overlap하는 interval은 같은 lane을 사용할 수 없고, interval graph coloring으로 최소 lane 수를 생각할 수 있습니다.이벤트 이동은 interval에 translation `T_Δ([s,e))=[s+Δ,e+Δ)`을 적용하는 연산입니다. 길이를 유지해야 하는 이동과 한쪽 경계를 바꾸는 resize는 다른 변환입니다. 종료가 시작보다 앞설 수 없다는 invariant를 유지하고, 달력 단위가 day인지 time인지에 따라 Δ의 algebra도 달라집니다. UI의 drag는 시간 구간에 대한 정확한 함수로 번역되어야 했습니다.색상과 제목은 interval geometry와 독립된 projection입니다. 행별 override와 범주 style, 전역 기본값은 cascade처럼 가까운 정의를 선택할 수 있습니다. 화면과 출력이 같은 style resolution을 사용하면 같은 event relation에서 같은 시각 의미가 나옵니다.캘린더도 별도 이벤트 데이터베이스가 아니었습니다. interval의 source는 base tuple이고 편집은 날짜 attribute의 update로 돌아갑니다. 같은 제목을 가진 이벤트가 여러 개 있어도 identity는 문자열이 아니라 tuple에 있습니다. 겉으로는 시간축의 카드지만 계산과 이력은 원래 relation의 transaction으로 유지되었습니다.Concurrent materializationview materialization은 비동기 질의와 렌더링의 결합입니다. revision r에서 시작한 느린 계산이 revision r+1의 빠른 계산보다 늦게 끝날 수 있습니다. 완료 시각은 최신성의 증거가 아니므로 결과가 어떤 source revision과 query parameter에서 나왔는지 함께 가져야 합니다. 현재 revision과 맞지 않는 결과는 commit할 수 없습니다.이는 optimistic concurrency control과 같습니다. 작업은 공유 화면을 잠그지 않고 독립된 후보 장면을 만들고, 마지막 commit에서 read revision이 여전히 유효한지 검사합니다. 유효하면 한 번에 교체하고 그렇지 않으면 후보를 버립니다. 사용자는 이전의 완전한 화면이나 새로운 완전한 화면만 보고 중간 장면은 보지 않습니다.렌더링이 실제 viewport measurement를 필요로 할 때도 계산 phase와 commit phase를 구분할 수 있습니다. 구조와 style을 먼저 준비하고 live geometry가 필요한 최소 구간만 commit 직전에 평가합니다. 측정이 DOM write와 번갈아 일어나면 forced layout 비용이 커지므로 read와 write를 batch로 나누는 것이 중요했습니다.view를 떠날 때는 materialized scene의 자원도 수명 끝을 맞습니다. event listener와 외부 resource handle, observer처럼 런타임에 등록된 자원은 garbage collector가 화면 tree만 지운다고 자동으로 모두 정리되지 않습니다. view lifecycle을 acquisition과 release가 짝을 이루는 resource scope로 다뤘습니다.base relation이 어느 경로에서 바뀌어도 현재 view는 같은 query를 새 revision에 적용합니다. 각 UI가 서로를 직접 호출하지 않고 revisioned dataflow를 통해 만납니다. 하나의 데이터에 여러 projection이 붙어도 lifecycle과 비동기 완료 순서가 서로를 침범하지 않는 구조가 되었습니다.Scene graph rendering표 이미지는 편집 화면을 캡처하지 않고 relation에서 새로운 scene graph를 만드는 편이 정확했습니다. 편집 UI에는 selection과 input, scroll처럼 출력 의미가 없는 상태가 섞여 있습니다. 출력 graph는 cell background, text, image와 border처럼 문서 의미가 있는 primitive만 가져야 합니다. source와 projection을 분리하면 viewport와 zoom이 결과를 바꾸지 않습니다.행 높이와 열 너비의 prefix sum은 격자 좌표를 연속 geometry로 바꿉니다. 열 j의 시작점은 `x_j = Σ_(kscene graph의 순서는 painter's algorithm을 따릅니다. 뒤에 그린 primitive가 앞의 결과 위에 합성되므로 background, fill, content와 edge의 순서가 시각적 의미를 결정합니다. 투명도가 있으면 단순 집합이 아니라 순서가 있는 sequence이고, clip은 이후 primitive의 정의역을 제한하는 상태로 작용합니다.얇은 선은 연속 geometry와 pixel grid 사이에서 예민합니다. 선의 중심이 반 pixel에 놓이는지, stroke가 어느 쪽으로 퍼지는지와 출력 scale에 따라 한 줄이 두 pixel에 걸릴 수 있습니다. 벡터 좌표에서는 정확한 edge를 유지하고 rasterization 단계에서 device transform과 stroke alignment를 고려해야 합니다.미리보기와 출력은 같은 scene graph를 서로 다른 camera로 관찰합니다. zoom과 pan은 graph를 바꾸지 않고 view transform만 바꿉니다. 내용이 바뀌었을 때만 scene을 다시 만들고 관찰 배율은 그대로 유지하면 편집 화면과 출판 결과가 하나의 기하 모델을 공유할 수 있습니다.Shaping geometry셀 텍스트의 폭은 글자 수에 비례하지 않습니다. Unicode sequence는 shaping을 거쳐 glyph sequence가 되고, ligature와 kerning, script-specific substitution이 실제 advance를 정합니다. 같은 문자라도 font face와 language, direction에 따라 다른 glyph와 위치가 나올 수 있습니다. typography geometry는 문자열 길이가 아니라 shaping result에서 시작해야 했습니다.한 line의 폭은 glyph advance와 positioning adjustment의 합입니다. 줄바꿈 후보는 언어별 line breaking class와 grapheme boundary를 따라야 하고, 한글과 CJK, 공백 기반 언어의 규칙은 다릅니다. 임의의 code unit 사이를 자르면 결합 문자와 emoji sequence가 깨질 수 있습니다. text segmentation은 geometry 이전의 언어학적 단계였습니다.행 높이는 line box들의 합과 padding으로 결정되지만 실제 ink bounds는 line box 밖으로 나갈 수 있습니다. italic overhang, diacritic과 underline을 clip하지 않으려면 advance geometry와 visual geometry를 구분해야 합니다. 병합 영역에서는 여러 행의 높이를 공유하므로 한 셀의 필요 높이가 어떤 행에 분배될지도 별도의 constraint가 됩니다.미리보기와 최종 출력이 다른 text engine을 사용하면 같은 font size에서도 line break가 달라질 수 있습니다. 따라서 공통 입력인 font file, shaping feature, language와 metric을 고정하고 두 renderer의 결과를 glyph position 수준에서 비교해야 합니다. 단순 CSS 문자열의 동등성은 typography의 동등성을 보장하지 않습니다.말줄임표도 남은 폭에 문자를 하나씩 줄이는 요령보다 prefix width의 단조성을 이용한 탐색 문제입니다. shaping된 prefix의 폭이 한계 이하인 가장 큰 경계를 찾고 ellipsis glyph의 advance를 함께 고려합니다. 여러 문자 체계와 결합 sequence에서도 올바른 경계만 후보로 사용하면서 표 안의 글자가 안정적으로 조판되었습니다.Planar edge와 closure격자선은 셀마다 독립된 사각형이 아니라 planar graph의 shared edge입니다. 인접한 두 face가 같은 edge를 소유하므로 stroke도 하나만 결정되어야 합니다. 각 셀이 자신의 테두리를 따로 그리면 같은 edge가 중복되고 두 style이 충돌합니다. topology를 먼저 만들고 edge style resolution을 한 번 수행하는 편이 정확했습니다.병합은 여러 face를 하나의 큰 face로 축약하는 graph contraction입니다. 내부 edge는 제거되고 외곽 boundary만 남습니다. 사각 병합 영역의 perimeter를 구해 outer edge에 style을 적용하면 병합 전 셀의 내부선이 출력에 남지 않습니다. 표의 테두리 문제는 CSS 예외보다 graph topology로 더 간단히 설명되었습니다.stroke는 path 중심을 따라 양쪽으로 퍼지므로 기하학적 boundary와 시각적 boundary가 다릅니다. 폭 t인 외곽선은 원래 영역의 Minkowski sum과 연결되고 바깥으로 `t/2`만큼 확장됩니다. 출력 box 안에 모두 포함하려면 clip을 늘리거나 path를 안쪽으로 offset해야 합니다. 선 굵기에 따라 viewBox가 잘리는 문제도 같은 기하로 해결할 수 있습니다.출력 scene이 독립 문서가 되려면 참조 자산의 transitive closure가 함께 있어야 합니다. scene이 image A를, A가 color profile B를, text가 font C를 필요로 한다면 root에서 도달 가능한 모든 정점을 모아야 합니다. 파일을 몇 개 인라인하는 요령보다 dependency graph의 closure를 계산하는 문제였습니다.자산 이름과 런타임 주소도 분리했습니다. 현재 세션의 handle은 영속 출력에서 의미가 없고, scene 안의 reference는 package 내부에서 다시 해소 가능한 identity를 가져야 합니다. 출력 전에 closure와 referential integrity를 검사하면서 이미지와 폰트가 빠지지 않는 자기완결 scene이 만들어졌습니다.Responsive partition넓은 표를 작은 화면에 단순 축소하면 글자와 열이 함께 읽을 수 없을 만큼 작아집니다. 반응형 표는 scale 문제가 아니라 열 집합의 partition 문제였습니다. 의미를 식별하는 key column은 각 조각에 반복하고 나머지 attribute를 여러 block으로 나누어 세로로 배치할 수 있습니다.열 집합 C를 k개의 순서 보존 부분집합으로 나눌 때 각 block의 폭과 전체 stack 높이가 달라집니다. 목표 aspect ratio를 ρ라고 하면 후보 partition P의 비용을 `|W(P)/H(P)-ρ|`와 block 간 불균형, 반복 열의 비용으로 구성할 수 있습니다. k와 경계 위치를 함께 탐색해 화면에 맞는 partition을 고르는 최적화가 됩니다.열 사이의 의미 관계도 비용에 넣을 수 있습니다. 서로 함께 읽히는 값은 같은 block에 둘 때 비용이 낮고, 매우 넓은 열은 혼자 두는 편이 나을 수 있습니다. 이는 sequence partition dynamic programming으로 풀 수 있습니다. prefix까지 j개 block으로 나눈 최소 비용을 저장하면 모든 분할을 전부 열거하지 않아도 됩니다.테마 변환은 색을 단순히 반전하는 연산이 아닙니다. 사용자가 명시한 semantic color와 시스템 기본값을 구분하고, 배경과 전경 사이의 상대 luminance와 contrast를 유지해야 합니다. sRGB channel 값보다 상대 휘도에서 contrast ratio를 계산하고 필요한 기본색만 다른 palette로 사상하는 편이 디자인을 보존합니다.모든 variant는 같은 relation과 scene builder에서 나와야 합니다. 데이터와 typography, 자산 closure는 공유하고 viewport constraint와 palette만 parameter로 바뀝니다. 별도의 축약 renderer를 만들지 않으면서 반응형 출력이 원본 표의 의미와 style을 유지했습니다.Sampling pipeline벡터 scene을 래스터 이미지로 만드는 과정은 연속 도형을 유한 격자에 표본화하는 일입니다. 출력 폭과 높이가 정해지면 각 pixel이 scene의 어느 면적을 대표하는지 결정되고, 단순한 점 sampling은 가는 선과 작은 글자에서 aliasing을 만들 수 있습니다. pixel footprint 위의 색을 적분하거나 충분한 supersampling 뒤 저역통과 filter로 줄이는 편이 정확합니다.투명 배경과 불투명 출력도 서로 다른 합성입니다. alpha를 보존하는 출력은 premultiplied color와 coverage를 함께 기록할 수 있지만 불투명 형식은 배경색 B와 `C = αF + (1-α)B`로 먼저 합성해야 합니다. 감마가 적용된 channel에서 바로 합성하면 경계 밝기가 달라지므로 가능한 경우 linear-light 공간에서 계산합니다.세로로 긴 scene을 여러 장으로 나누는 것은 행 sequence의 pagination입니다. 각 조각은 같은 열 geometry와 style을 공유하고 header는 반복되는 template이 됩니다. 최대 높이와 의미 단위를 constraint로 두고 row interval을 분할하면 통짜 출력과 분할 출력이 같은 scene builder를 사용할 수 있습니다.칸반과 캘린더도 화면 DOM이 아니라 자신의 관계 구조에서 scene graph를 만듭니다. 칸반은 quotient class와 order를 lane geometry로, 캘린더는 interval graph coloring 결과를 시간 격자로 투영합니다. 화면과 출력이 같은 base relation과 수학적 projection을 사용하므로 capture 환경에 따른 차이가 생기지 않습니다.최종 자산은 완성된 결과만으로 끝나지 않습니다. 어떤 relation revision과 query, viewport와 palette에서 생성되었는지 provenance가 있어야 다시 편집하고 재생성할 수 있습니다. 큰 binary를 어떻게 옮기는지보다 결과와 원본 사이의 의미 연결을 유지하는 것이 출판 pipeline의 핵심이었습니다.Round-trip equivalence출판물에는 래스터 결과가 들어가더라도 그 뒤의 편집 원본은 relation과 query로 남아 있어야 합니다. 이미지의 pixel은 특정 revision에서 계산된 projection이고 source document와 동등하지 않습니다. 결과와 provenance를 함께 연결하면 독자는 안정적인 그림을 보고 제작자는 원래 데이터로 돌아갈 수 있습니다.왕복의 정확성은 byte identity보다 의미 동등성으로 정의했습니다. 원본 상태 x를 렌더 R로 보내고 다시 편집 원본을 여는 연산 O를 거쳤을 때 `O(R(x))`가 x를 완전히 복원하는 것은 불가능합니다. rasterization은 정보를 잃기 때문입니다. 대신 결과 자산이 별도의 provenance edge로 x를 가리키게 하여 역변환을 추측하지 않고 원본을 직접 찾습니다.새 revision의 결과를 기존 레이아웃 객체에 적용할 때는 identity와 value를 분리합니다. 페이지에서의 위치와 사용자 배치는 identity 쪽에 남고, 새 scene의 aspect ratio와 pixel은 value로 교체됩니다. 교체는 완성된 후보를 검증한 뒤 원자적으로 적용해야 이전의 정상 결과가 중간 실패로 사라지지 않습니다.사람이 UI에서 실행하는 동작과 자동화된 입력도 같은 typed command로 환원했습니다. base relation을 바꾸는 모든 명령은 type과 invariant, transaction 경계를 통과하고 view와 출력은 새 revision을 다시 materialize합니다. 입력 방식마다 별도 수정 경로를 만들지 않아 같은 수학적 계약이 유지되었습니다.그리드와 칸반, 캘린더 사이의 tuple 변경, quotient와 interval 변환, 연속 비동기 materialization, 여러 viewport와 palette, 글꼴과 이미지 closure, 긴 표 partition과 원본 왕복을 반복 검수했습니다. 화면의 데이터와 각 scene 출력, 출판물에 들어간 결과가 같은 revision을 가리키는 것을 확인했습니다. 하나의 표 데이터를 여러 출판 형식으로 바꾸고 다시 원본 편집으로 돌아오는 테이블 제작 시스템은 완전하게 구현되고 안정화되었습니다.이전글목록으로다음글저작권 고시Copyright Notice본 웹사이트의 모든 디자인 결과물 및 영상에 대한 저작권은 Abstract Cloud에 있으며, 저작권법 및 관련 법령에 의해 보호받습니다. 웹, 영상, 본문, 표지, 내지 디자인을 포함한 모든 콘텐츠는 저작권자의 자산으로, 사전 동의 없이 무단 복제, 배포, 2차 저작물 제작, 온라인 공유 등을 금지합니다. 이를 위반할 시, 저작권법에 따라 민형사상 책임을 질 수 있습니다. 정당한 구매와 저작권 보호는 창작자의 권리를 지키며, 더 나은 작품으로 보답할 힘이 됩니다.저작권자: Abstract Cloud | 대표자: 배창규(uragen)
© Abstract Cloud. All Rights Reserved.
HOMEFAQ이용 약관개인정보 이용방침help@opkle.app
010-2747-3403
상호 :추상적 형상 디자인(Abstract cloud)  |  대표자 :배창규사업자등록번호 :249-74-00533통신판매업 신고번호 :2025-의정부송산-0634주소 :경기도 의정부시 부용로 49, 108동 402호웹의 모든 콘텐츠, 디자인, 소스 코드에 대한
저작권은 Opkle에게 있습니다.
img default description