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텍스트힙이란 무엇인가texthip1910111213...18 옵클 에디터 개발기: 같은 데이터를 표와 보드, 캘린더로스프레드시트 편집이 완성된 뒤에는 그 데이터를 어떤 결과로 보여줄 것인지가 남았습니다. 처음 목표는 구글 시트처럼 값을 편집하고 출판물에 넣을 표 이미지를 만드는 것이었지만, 타입이 있는 열과 안정적인 레코드 구조까지 갖추고 나니 같은 데이터에서 표만 만들 이유가 없었습니다. 상태 열은 칸반의 레인이 될 수 있고, 날짜 열은 캘린더의 시간축이 될 수 있습니다.나는 그리드, 칸반, 캘린더를 서로 다른 문서로 만들지 않았습니다. 하나의 SQLite 문서를 여러 방식으로 투영하고, 어느 화면에서 값을 바꾸더라도 같은 셀 데이터로 돌아오게 했습니다. 뷰는 데이터를 복제하지 않고 어떤 열을 기준으로 어떻게 배치할지, 카드에 무엇을 보여줄지, 색과 순서를 어떻게 기억할지만 저장합니다.출력도 화면을 캡처하는 방식으로 만들지 않았습니다. 그리드용 표, 칸반 보드, 캘린더 월 화면을 각각 데이터에서 네이티브 SVG로 다시 조판하고 PNG나 JPEG로 변환했습니다. 모바일과 데스크탑, 라이트와 다크를 같은 원본에서 만들고 EPUB과 레이아웃 에디터로 다시 보낼 수 있게 했습니다. 표 에디터가 데이터 입력 도구에서 출판용 시각 자료 제작기로 확장된 과정입니다.One table, many views뷰 정의는 SQLite 문서 안에 시트 ID, 이름, 종류, 순서와 config를 가진 row로 저장했습니다. 프로젝트 manifest에는 시트별로 마지막에 열어 본 뷰 ID만 기억했습니다. 표 프로젝트를 다른 장치로 옮겨도 뷰와 열 식별자가 DB 스냅샷 안에서 함께 이동하므로 마지막 작업 화면을 그대로 복원할 수 있습니다.기본 그리드는 별도의 뷰 row가 없는 상태로 두었습니다. 칸반이나 캘린더 버튼을 누르면 해당 시트에서 같은 종류의 뷰를 찾아 열고, 없다면 한 번만 생성합니다. 같은 버튼을 다시 누르면 그리드로 돌아갑니다. 시트마다 독립적인 마지막 뷰를 가지기 때문에 첫 시트는 칸반, 두 번째 시트는 캘린더로 열어 둔 상태도 유지됩니다.비그리드 뷰가 활성화되면 셀 편집용 toolbar와 렌더 영역 버튼을 숨기고, 그 자리에 뷰 전용 상단 bar와 오른쪽 panel을 표시했습니다. underlying grid DOM은 파괴하지 않고 잠시 숨겼습니다. 다시 그리드로 돌아왔을 때 시트 geometry와 선택, 스크롤을 새로 구성하지 않아도 즉시 이어서 편집할 수 있습니다.뷰 config를 바꿀 때는 live SQLite만 갱신하는 것으로 끝내지 않았습니다. 그룹 열, 카드 표시 열, 카드 순서, 캘린더 기준 날짜와 색상처럼 화면에서는 즉시 바뀌지만 DB 스냅샷 저장을 놓치기 쉬운 상태를 모두 자동저장 대상으로 만들었습니다. 뷰 설정도 셀 값과 같은 프로젝트 상태로 취급했습니다.안정적인 열과 행뷰가 열을 숫자 좌표로 기억하면 A열 앞에 새 열을 삽입하는 순간 모든 설정이 다른 열을 가리키게 됩니다. 그래서 열마다 위치와 별개의 안정 ID를 발급하고, 칸반의 그룹 기준과 카드 표시 목록, 캘린더의 시작·종료 날짜와 색상 기준은 모두 그 ID로 저장했습니다. 화면을 그릴 때만 현재 열 좌표로 해석합니다.행에도 같은 원칙을 적용했습니다. 캘린더에서 특정 이벤트의 색을 행 번호로 기억하면 위쪽에 레코드를 삽입했을 때 색이 옆 이벤트로 이동합니다. 개별 이벤트 설정이 처음 필요해지는 시점에 안정적인 행 ID를 만들고, 색상 override는 이 ID를 key로 보관했습니다. 행 삽입과 삭제 뒤에도 레코드의 시각 설정이 따라갑니다.칸반의 카드 순서는 조금 다른 상태를 가집니다. 카드 순서 배열은 단순한 정렬 결과뿐 아니라 칸반에서 만든 빈 카드가 존재한다는 등록부 역할도 합니다. 그룹 값 하나뿐인 카드를 미지정 레인으로 옮겨 셀 값이 모두 비어도 순서 배열에 등록되어 있으면 카드가 사라지지 않습니다. 값이 있는 행과 등록된 행의 합집합이 실제 카드 목록이 됩니다.이 안정 ID들은 undo 스냅샷과 프로젝트 export에도 포함했습니다. 열 타입 변경이나 행·열 이동을 되돌렸을 때 뷰 config가 존재하지 않는 대상을 가리키지 않으며, 다른 장치에서 프로젝트를 열어도 시트 내부의 관계가 유지됩니다. 화면 좌표를 영구 식별자로 사용하지 않은 것이 다중 뷰의 기초가 되었습니다.Kanban projection칸반은 선택 또는 다중 선택 타입의 열을 그룹 기준으로 사용했습니다. 뷰에 지정된 그룹 열이 없으면 첫 번째 선택 열을 찾아 자동으로 연결하고, 선택 옵션의 순서와 라벨, 색을 그대로 레인으로 만들었습니다. 어떤 옵션에도 속하지 않는 행을 위한 미지정 레인도 항상 별도로 두었습니다.카드의 제목은 타이틀 열에서 가져오고, 나머지 property 중 사용자가 고른 열만 카드 본문에 표시했습니다. 선택 값은 color chip으로, 체크박스는 상태 기호로, 날짜와 텍스트는 이름과 값을 가진 한 줄로 표현했습니다. 표시 열을 따로 고르지 않으면 그룹 열을 제외한 모든 이름 있는 property를 사용해 처음 연 칸반도 바로 쓸 수 있게 했습니다.카드 drag and drop은 레인 값과 순서를 동시에 갱신합니다. 단일 선택 열에서는 도착 레인의 라벨로 값을 교체하고, 다중 선택 열에서는 출발 레인 라벨만 제거한 뒤 도착 라벨을 추가해 나머지 분류를 보존했습니다. 미지정 레인으로 옮길 때는 해당 그룹 값만 비웁니다. 드롭한 y좌표와 카드 rect의 중점을 비교해 어느 카드 앞에 들어갈지도 계산했습니다.레인 자체도 drag으로 순서를 바꿀 수 있게 했습니다. 레인 이름과 색을 바꾸거나 삭제하면 원본 선택 옵션이 갱신되므로 그리드의 chip과 칸반이 함께 바뀝니다. 레인 배경은 전체 공통색과 개별색 모드를 지원하고, 제목 글자색과 카드 밀도, 빈 레인 숨기기, 카드에 보일 열도 뷰 config로 저장했습니다.카드는 더블클릭하거나 우클릭하면 property 편집 popup을 엽니다. text와 title은 input, 선택 값은 같은 option popup, 체크박스는 toggle, 날짜는 달력으로 편집하고 모두 원래 셀에 커밋합니다. 카드 생성과 복제, 삭제도 별도 객체 모델을 만들지 않고 행의 셀 데이터를 조작했습니다. 칸반에서 한 작업이 그리드로 돌아가는 순간 그대로 보입니다.Calendar projection캘린더는 날짜 타입 열 하나를 시작 날짜로 사용합니다. 뷰에 기준 열이 없으면 첫 번째 날짜 열을 찾아 연결하고, 날짜 열이 전혀 없다면 빈 달력을 억지로 만들지 않고 어떤 property가 필요한지 안내했습니다. 타이틀 열은 이벤트 제목이 되고, 두 번째 날짜 열을 지정하면 여러 날에 걸친 기간 이벤트가 됩니다.색상은 세 단계로 결정했습니다. 특정 행에 직접 지정한 색이 가장 먼저이고, 그 값이 없으면 선택 타입의 색상 기준 열에서 첫 라벨의 옵션색을 가져오며, 마지막으로 캘린더 전역 기본색을 사용합니다. 글자색은 별도의 전역 설정으로 두었습니다. 화면 캘린더와 이미지 출력이 같은 색 해석 함수를 호출해 결과가 달라지지 않게 했습니다.기본 모드는 보기 전용으로 두고 편집 mode를 켰을 때만 날짜 클릭, 이벤트 클릭, drag와 기간 resize를 활성화했습니다. 빈 날짜를 누르면 그 날짜를 가진 새 행을 만들고 카드 편집 popup을 열었습니다. 이벤트를 옮기면 시작 날짜를 바꾸고, 기간 이벤트라면 이동한 일수만큼 종료 날짜도 함께 이동했습니다. 양끝을 resize한 경우에는 시작과 종료를 모두 다시 기록했습니다.all-day event의 날짜 객체는 시작과 종료에 같은 인스턴스를 넘기지 않았습니다. 캘린더 라이브러리가 종료일을 조정하면서 공유 객체까지 바꾸면 시작일이 하루 밀릴 수 있기 때문입니다. 각 날짜는 별도 객체로 복제하고, 종료가 시작보다 빠르면 시작 날짜로 보정했습니다. 현재 보던 월도 재렌더 전에 읽어 두었다가 새 인스턴스에 다시 적용했습니다.이벤트 우클릭에서는 제목과 색을 바꾸거나 행을 삭제할 수 있게 했습니다. 같은 제목의 이벤트가 여러 개 있어도 문자열이 아니라 이벤트 ID에서 실제 행으로 가는 map을 사용했습니다. 겉으로는 달력 위의 일정 카드지만 모든 변경은 타입 검사와 이력, 자동저장이 적용되는 원래 표의 변경으로 처리됩니다.Detached rendering칸반과 캘린더는 DB를 읽는 동안 여러 번 `await`를 지나갑니다. live overlay를 먼저 비운 뒤 데이터를 읽으면 잠깐 빈 화면이 노출되고, 빠르게 설정을 연속 변경하면 먼저 시작한 느린 렌더가 나중 결과를 덮을 수 있습니다. 그래서 새 화면을 detached DOM에 전부 만든 뒤 마지막에 한 번만 교체했습니다.각 뷰 렌더에는 증가하는 sequence를 붙였습니다. DOM을 commit하기 직전에 자신이 가장 최근 요청인지 확인하고, 더 새로운 렌더가 시작되었다면 옛 결과를 버렸습니다. 칸반 카드 순서나 색을 빠르게 바꿔도 오래된 비동기 결과가 화면과 config를 되돌리지 않습니다.캘린더는 살아 있는 layout box가 있어야 크기를 정확히 측정하는 특성이 있어 swap 순서를 더 세밀하게 잡았습니다. detached fragment에 shell을 만들고, 실제 calendar render 직전에만 overlay에 붙였습니다. 이전 인스턴스는 새 DOM이 준비될 때까지 화면에 남겨 두고 commit 시점에 destroy해 window listener와 내부 상태가 누적되지 않게 했습니다.그리드의 데이터가 undo, fill, card edit 등 어느 경로에서 바뀌어도 현재 활성 뷰를 가볍게 다시 투영했습니다. 반대로 뷰를 떠날 때는 toolbar와 오른쪽 panel을 원상복구하고 캘린더 instance를 정리했습니다. 하나의 데이터에 여러 UI가 붙어도 서로의 lifecycle을 침범하지 않는 상태가 되었습니다.표를 다시 그리는 SVG표 이미지 생성은 편집 그리드를 캡처하지 않았습니다. 편집 DOM에는 선택 overlay와 sticky header, input, scroll clip처럼 출력에 필요 없는 요소가 많고, 브라우저 zoom이나 화면 크기에 따라 pixel 결과도 달라집니다. SQLite에서 계산값과 스타일, 병합, 행·열 크기를 다시 읽어 순수 SVG를 새로 조립했습니다.먼저 셀 값과 스타일, 병합 범위의 bounding box를 합쳐 실제 사용 범위를 구했습니다. 사용자가 렌더 영역을 직접 지정했다면 그 사각형과 시트 크기, 활성 범위의 교집합만 출력했습니다. property 이름이 있는 열은 값이 비어 있어도 활성 열로 포함하고, 헤더 출력과 미출력은 별도 상태로 저장했습니다.열 너비를 누적해 x좌표를 만들고 행 높이를 누적해 y좌표를 만들었습니다. 병합 anchor는 span에 포함된 폭과 높이를 합치고 covered cell은 건너뜁니다. 모든 크기를 정수 좌표에 맞춰 얇은 선이 subpixel에서 두 픽셀로 번지는 것을 막았습니다. viewBox는 표의 실제 bounding box와 정확히 같게 두었습니다.렌더는 배경과 셀 채움, 콘텐츠, 기본 격자선, 외곽 frame, 명시 테두리 순으로 pass를 나눴습니다. 콘텐츠가 선을 덮지 않고 셀별 테두리가 기본 격자보다 위에 오도록 z-order를 확정한 것입니다. 둥근 모서리를 사용할 때는 전체 표에 clip path를 만들고 배경과 헤더가 radius 바깥으로 나오지 않게 했습니다.편집 화면 아래에는 같은 SVG를 zoom과 pan 가능한 미리보기로 붙였습니다. CSS transform으로 bitmap처럼 늘리지 않고 SVG의 실제 width와 height를 zoom 배율에 맞춰 바꾸어 분수 배율에서도 브라우저가 벡터를 다시 그리게 했습니다. artboard 크기가 달라지면 자동으로 화면맞춤을 실행하고, 내용만 바뀐 경우에는 사용자가 보던 zoom과 위치를 유지했습니다.Typography geometry셀 텍스트는 글자 수로 폭을 추정하지 않았습니다. offscreen canvas에 실제 family, size, weight와 italic을 적용해 `measureText()`로 폭을 읽었습니다. 공백이 있는 문장은 마지막 공백을 우선 줄바꿈 지점으로 사용하고, 공백이 없는 한글과 CJK 문자열은 글자 단위로 끊었습니다. 원문의 명시적인 줄바꿈도 그대로 보존했습니다.줄바꿈이 켜진 셀은 열 폭에서 왼쪽·오른쪽 padding을 뺀 공간으로 line을 만들고, line 수와 font size, line-height, 위·아래 padding으로 필요한 높이를 계산했습니다. 저장된 행 높이보다 필요한 값이 크면 출력 단계의 행 높이만 늘렸습니다. 여러 행을 차지하는 병합 셀은 자동 높이 계산에서 따로 다루어 같은 공간이 중복 증가하지 않게 했습니다.라이브 미리보기에서는 SVG 안의 `foreignObject`와 HTML을 사용했습니다. 브라우저 CSS가 글꼴 로드와 줄바꿈, flex 정렬을 자연스럽게 처리하기 때문입니다. 하지만 SVG를 ``로 바꿔 canvas에 그릴 때 `foreignObject`는 taint와 호환성 문제가 생길 수 있습니다. 실제 추출에서는 같은 셀을 네이티브 ``와 ``로 다시 그렸습니다.두 경로가 서로 다른 스타일 해석을 가지면 미리보기와 PNG가 조금씩 달라집니다. 그래서 padding clamp, 가로·세로 정렬, 글꼴과 색, 행간과 자간, 밑줄, 줄바꿈 여부는 하나의 공통 resolver에서 확정했습니다. HTML 경로는 CSS로, native 경로는 좌표와 text anchor로 표현만 다르게 했습니다. 한 줄 넘침에는 실제 폭을 재면서 말줄임표를 넣고, 세로 공간을 넘는 line은 셀 clip 안에서 잘랐습니다.경계선과 자산 인라인격자선은 셀마다 사각형 stroke를 그리지 않았습니다. 인접한 두 셀이 같은 경계를 각각 그리면 선 두께가 두 배가 되고 교차점이 흐려집니다. 각 셀의 위쪽과 왼쪽 내부선만 한 번씩 그리고, 표 전체의 바깥쪽은 하나의 frame으로 그렸습니다. 점선과 파선은 두께에 맞춰 dash 간격과 line cap을 계산했습니다.SVG의 stroke는 path 중심을 기준으로 양쪽에 퍼지므로 외곽선 절반이 viewBox 밖으로 나갈 수 있습니다. frame을 선 두께의 절반만큼 안쪽으로 이동하고 width와 height를 그만큼 줄였습니다. 셀에 명시한 테두리도 표 가장자리에 닿는 변은 같은 inset 규칙을 사용했습니다. 얇은 1px 선부터 굵은 테두리까지 래스터에서 잘리지 않고 맞물립니다.셀 이미지는 저장소의 blob을 찾아 data URL로 바꾸고 `preserveAspectRatio="xMidYMid meet"`를 적용했습니다. live preview에서 사용한 object URL은 렌더를 교체할 때 모두 revoke했습니다. 출력 SVG가 외부 세션 주소에 의존하지 않고 자체적으로 완결된 문서가 되므로 EPUB 삽입과 다운로드에서도 이미지가 빠지지 않습니다.폰트도 같은 방식으로 인라인했습니다. SVG 안에서 실제로 사용된 family와 weight 조합을 수집하고, 기본 폰트는 정적 자산에서, 사용자 폰트는 등록된 blob URL에서 읽어 data URL `@font-face`를 만들었습니다. 같은 family의 Regular와 Bold를 별도 face로 넣고 SVG가 이미지로 디코드되기 전에 필요한 font source를 모두 문서 안에 포함했습니다.Responsive variants데스크탑용 표를 폭만 줄여 모바일 이미지로 만들면 열이 지나치게 좁아집니다. 모바일 출력은 첫 번째 타이틀 열을 고정하고 나머지 데이터 열을 여러 그룹으로 나누어 세로로 쌓았습니다. 각 블록에 타이틀 열을 반복해 어떤 조각에서도 행의 주체를 읽을 수 있게 했습니다.그룹 수는 고정하지 않았습니다. 원본 열 너비와 행 높이로 각 후보 그룹 수의 block width와 stack height를 근사하고, 가로세로 비율이 목표 비율에 가장 가까운 경우를 선택했습니다. 실제 각 block은 이미 검증한 표 SVG renderer에 비연속 열 순서를 넘겨 만들고, 마지막에 nested SVG로 y축에 합성했습니다. 별도의 축약 renderer를 새로 만들어 스타일이 빠지는 일을 피했습니다.다크 모드는 사용자가 지정한 색을 무조건 뒤집지 않았습니다. 본문 흰색, 기본 텍스트, 기본 격자선과 헤더처럼 라이트 기본값과 같은 항목만 다크 palette로 치환했습니다. 셀별 채움과 글자색, 사용자가 바꾼 문서 배경은 그대로 존중했습니다. 디자인한 표를 보존하면서 미지정 기본색만 읽기 좋은 다크 상태로 바뀝니다.최종 variant는 데스크탑 라이트, 모바일 라이트, 모바일 다크, 데스크탑 다크 네 가지입니다. 같은 렌더 영역과 데이터, 폰트와 이미지에서 만들고 파일명에 form과 theme을 명확히 남겼습니다. 네 장은 하나의 zip으로 내려받거나 그대로 EPUB에 삽입할 수 있습니다. 미디어쿼리용 표 이미지를 다른 프로그램에서 네 번 다시 만드는 과정이 사라졌습니다.출판 파이프라인SVG는 최종적으로 긴 변이 1980px이 되도록 출력 크기를 계산했습니다. viewBox의 논리 좌표는 그대로 두고 width와 height만 같은 비율로 키운 뒤 브라우저 SVG 엔진으로 image를 만들고 canvas에 그렸습니다. PNG는 투명도를 유지하고 JPEG는 흰 배경을 먼저 채운 뒤 품질을 지정했습니다.세로로 긴 표는 사용자가 지정한 수만큼 데이터 행을 나누어 여러 장으로 추출할 수 있게 했습니다. 각 조각은 같은 열 범위를 가지며, 헤더 출력 mode에서는 동일한 property header를 반복합니다. 분할 수가 데이터 행보다 많으면 중단하고, 장수가 과도하게 많으면 한 번 더 확인했습니다. 통짜 표와 분할 표가 같은 렌더 함수를 사용합니다.칸반과 캘린더도 화면 DOM을 캡처하지 않고 별도의 native SVG를 만들었습니다. 칸반은 레인과 카드의 실제 데이터로 보드 폭과 카드 높이를 계산하고, 캘린더는 현재 표시 월의 주 수를 구한 뒤 기간 이벤트를 주 단위 segment로 나누어 겹치지 않는 lane에 배치했습니다. 이벤트가 한 셀에 너무 많으면 보이는 lane 수를 제한하고 나머지 개수를 표시했습니다.EPUB 삽입 전에는 현재 프로젝트를 먼저 저장하고, 이미지 한 장씩 zip으로 묶어 부모 편집기와 공유하는 중계 저장소에 넣었습니다. message에는 큰 binary를 싣지 않고 key와 표 프로젝트 ID만 보냈습니다. 부모는 순서대로 이미지를 꺼내 챕터에 삽입하며, 표 이미지는 원본 표 프로젝트 ID를 기억해 나중에 다시 편집할 수 있습니다.레이아웃으로 되돌아가기레이아웃 에디터에서 새 표를 만들면 빈 표 프로젝트를 생성하고 안정적인 프로젝트 ID를 먼저 돌려주었습니다. 기존 표 이미지를 다시 열 때는 그 ID로 원본 SQLite 문서를 복원합니다. 사용자가 보게 되는 것은 JPEG 한 장이지만 그 뒤에는 셀과 타입, 뷰와 자산을 가진 편집 가능한 원본이 계속 연결되어 있습니다.표 편집기를 닫을 때는 일반 저장 flush를 먼저 끝내고 현재 렌더 영역을 JPEG로 만들었습니다. 결과 blob과 실제 artboard 크기를 session 교환소에 기록한 뒤 부모에 준비 완료를 알렸습니다. 레이아웃 에디터는 같은 오브젝트의 위치와 표시 폭을 유지하고 새 종횡비에 맞춰 높이와 blob만 교체합니다. 결과가 비어 있다면 기존 이미지를 없애지 않고 원본 연결을 유지했습니다.사람이 toolbar와 popup으로 실행하는 기능은 선택 범위와 시트 상태를 받는 명령 계층으로도 정리했습니다. 값 입력과 정리, 서식, 행·열 변경, 뷰 전환, 렌더 영역, 이미지 추출이 같은 검증된 메서드를 통과합니다. 표 이미지 분석 결과를 받아들이는 경우에는 기존 시트를 덮지 않고 새 시트를 만든 뒤 열 이름과 타이틀 불변식을 설정하고, 쓰기에 실패하면 부분 시트를 제거하도록 했습니다.그리드와 칸반, 캘린더 사이의 값 변경, 레인과 이벤트 drag, 빠른 연속 재렌더, 다크·모바일 조합, 커스텀 폰트와 셀 이미지, 긴 표 분할, PNG·JPEG, EPUB 삽입, 레이아웃 왕복, 프로젝트 재열기와 다른 장치 이식까지 모두 반복 검수했습니다. 화면에서 편집한 데이터와 각 SVG 출력, EPUB과 레이아웃에 들어간 이미지가 일치하는 것을 확인했습니다. 하나의 표 데이터를 여러 출판 형식으로 바꾸고 다시 원본 편집으로 돌아오는 테이블 제작 시스템은 완전하게 구현되고 안정화되었습니다.이전글목록으로다음글저작권 고시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