sound-parade/src/pages/entry.html

403 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>흐름을 향하여 걷는 | 소개</title>
<link rel="stylesheet" href="/default.css" />
<style>
#defaultCanvas0 {
display: block;
}
.clear {
display: block;
}
img {
width: 100%;
max-width: 500px;
}
#p5 {
display: inline-block;
}
.tools {
padding: 1em 1em;
display: inline-block;
vertical-align: top;
}
.penselect {
margin: 1em 0em;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.10.2/p5.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@500&display=swap" rel="stylesheet" />
</head>
<body>
<div class="bg"></div>
<div class="content">
<div class="first"><a href="/en/entry">EN</a></div>
<div class="second"><a href="/" target="_blank">퍼레이드</a></div>
<div class="notice">
<section>
<h1>흐름을 &nbsp;향하여 &nbsp;걷는</h1>
<p>
《흐름을 향하여 걷는》은 사운드 퍼레이드가 열리는 온라인 공간입니다.
</p>
<p>
퍼레이드가 진행되는 이 곳, <a href="https://walkingtowardstheflow.xyz">walkingtowardstheflow.xyz</a>는 누구나 소리를 업로드하고 감상할 수 있는 사운드 피드(feed)이자 소리의 행렬이 지나는 길이기도 합니다. 이는 각자의 시공간에 흩어져 살아가는 우리가 고개를 잠깐 돌리기만 하면 함께 걸을 수 있는 흐름이 아주 가까운 곳에 있음을 말합니다.
</p>
<p>
《흐름을 향하여 걷는》은 오프라인 공연으로 함께 하실 수도 있습니다. 11월 27일(일) 공연 당일에는 웹사이트에 모인 소리들이 물리적 공간에 펼쳐집니다. 이 공연은 함께 걷고 멈추며, 흐름에 공감해 주신 분들의 소리를 듣는 참여형 공연입니다.
</p>
<p>
소리는 본질적으로 공기를 진동하는 에너지이며, 이 진동은 우리의 고막 뿐 아니라 온 몸과 피부를 온통 진동하는 촉각적인 접촉입니다. 우리는 소리들의 부드러운 접촉을 통해 그 소리의 발신자들이 부드럽게 우리의 손을 잡아 퍼레이드로 초대하고 있다는 것을 어렵지 않게 눈치챌 수 있습니다.
</p>
<p>
소리가 흐르는 길은 영혼이 회귀하는 저 광맥과 같이, 별들이 순회하는 저 은하수와 같이, 언제나 그 자리에 있으면서 당신이 소리를 듣고 있든 그렇지 않든 무관하게 흐르고 있습니다.
</p>
<p>
당신은 혼자이지만 언제나 원하는 순간에 이 흐름을 향해 걸어 들어올 수 있으며, 당신이 원한다면 함께 걷고, 따라걷고, 멈춰서서 바라보고, 당신의 소리, 당신의 만지는 손길들을 이 흐름에 띄워 보낼 수도 있습니다. 우리는 그렇게 손을 잡을 수 있습니다.
</p>
<p>
이 영원한 퍼레이드의 영속성은 우리에게 최후의 안도를 줍니다. 때로는 자신이 위성궤도를 무한히 돌고 있는 우주 부스러기(debris)라는 생각이 들다가도, 그것이 관측되는 순간 더는 외롭거나 잊혀짐이 아니라 기억됨이 됩니다.
</p>
<p>
소리 모집에 많은 참여 부탁드립니다.<br>
*혐오표현은 《흐름을 향하여 걷는》에 모인 사람들이 보호해야 할 표현의 자유에 포함되지 않으므로 삭제될 수 있습니다.
</p>
<br>
<div class="info">
<p>
소리 모집<br/>
2022년 11월 13일(일)2022년 11월 26일(토)
</p>
<p>
퍼포먼스<br/>
2022년 11월 27일(일), 16:00
</p>
<p>
장소<br/>
<a
서울시립미술관 서소문본관 1층 로비, 2층 러닝스테이션, 옥상정원
</p>
<p>
작가<br/>
<a href="https://dianaband.info" target="_blank">다이애나밴드</a> X <a href="https://cgyoon.kr/" target="_blank">윤충근</a> (문의: wonjung24@gmail.com)
</p>
<p>
퍼레이드 구성<br/>
무리0 약속들 — 무리1 깃발들 — 무리2 신체들 — 무리3 사물들 — 무리4 누구들
</p>
</div>
</section>
<section class="participation">
<h1>참여 방법</h1>
<ol>
<li>1. 무리의 특성에 따라 녹음기기나 핸드폰으로 30초 가량의 소리를 녹음해주세요.
<ol class="category">
<li>• 무리1 깃발들 — 좋아하는 것, 가치에 대해 다섯 번 외쳐주세요.</li>
<li>• 무리2 신체들 — 몸에서 나는 소리를 녹음해 보아요. 박수, 휘파람도 좋아요.</li>
<li>• 무리3 사물들 — 주변 사물들의 소리를 찾아 주세요. 뽁뽁이 소리, 구슬 소리</li>
<li>• 무리4 누구들 — 누구의 소리를 모아주세요. 반려동물, 물 소리, 산책의 장소</li>
</ol>
</li>
<li>2. 녹음한 소리 파일을 업로드해주세요.</li>
<li>3. 소리의 제목과 묘사을 입력한 뒤, 소리의 모양을 그려 제출해주세요.</li>
</ol>
</section>
<section>
<h1>소리 제출</h1>
<form id="form" method="POST" enctype="multipart/form-data">
<ul class="submit">
<li>
<h3>1. 소리 유형</h3>
<ul>
<li>
<input type="radio" name="group" autocomplete="off" value="flag" /> 무리1 깃발들
</li>
<li>
<input type="radio" name="group" autocomplete="off" value="body" /> 무리2 신체들
</li>
<li>
<input type="radio" name="group" autocomplete="off" value="object" /> 무리3 사물들
</li>
<li>
<input type="radio" name="group" autocomplete="off" value="any" required /> 무리4 누구들
</li>
</ul>
</li>
<li>
<h3>2. 소리 파일</h3>
<input type="file" name="audiofile" autocomplete="off" required />
</li>
<li>
<h3>3. 소리 제목</h3>
<input id="title" name="title" type="text" autocomplete="off" required />
</li>
<li>
<h3>4. 소리 묘사</h3>
<input id="comment" name="comment" type="text" autocomplete="off" required />
</li>
<!-- pixels.png ~ made with p5.js -->
<li class="noscroll">
<h3>5. 소리 모양</h3>
<div>
<div id="p5">
</div>
<div class="tools">
<div>
<input class="penselect" type="radio" value="pencil" name="penselect" id="penselect-pencil" autocomplete="off" checked>
<label for="penselect-pencil">연필</label>
</div>
<div>
<input class="penselect" type="radio" value="erasor" name="penselect" id="penselect-erasor" autocomplete="off">
<label for="penselect-erasor">지우개</label>
</div>
</div>
</div>
<button type="button" class="clear">다시그리기!</button>
</li>
<li>
<h3>6. 비밀 번호</h3>
<input id="pass" name="pass" type="text" maxlength="2" pattern="^\d{2}$" title="암호는 숫자x2개로 해주세요." required />
<small>두 자리 숫자를 입력해주세요. 업로드한 소리를 삭제할 때 쓰입니다.</small>
</li>
<li>
<input id="submit" type="submit" value="보내기!" />
</li>
</ul>
<p>
↓ 
</p>
</form>
</section>
<section>
<h1>소리 목록</h1>
<p>제출해주신 소리는 아래 목록에 시간순으로 쌓입니다. 각각의 모양을 클릭하면, 모양에 해당하는 소리를 듣고 소리에 대한 정보를 확인할 수 있습니다.</p>
{{#each list}}
<div class="items" foldername="{{this.foldername}}">
<details>
<summary><img class="drawing" src="https://p.dianaband.info/public/sound-parade/{{this.foldername}}/pixels.png" /></summary>
<audio class="sound" preload="none" controls>
<source src="https://p.dianaband.info/public/sound-parade/{{this.foldername}}/audio.mp3" type="audio/mpeg">
</audio>
<ul class="soundinfo"><hr>
<li>유형 | <div class="group">{{this.group}}</div></li><hr>
<li>제목 | <div class="title">{{this.title}}</div></li><hr>
<li>묘사 | <div class="comment">{{this.comment}}</div></li><hr>
</ul>
<button class="preview" type="button" onclick="javascript:window.open('/preview/{{this.foldername}}', '_blank');">미리보기</button>
<button class="delete" type="button" onclick="del(this)">삭제</button>
</details>
</div>
{{/each}}
</section>
</div><!-- div class="notice" -->
</div><!-- div class="content" -->
<script>
// -- https://stackoverflow.com/a/39577640
function dataURLtoBlob(dataURL) {
let array, binary, i, len;
binary = atob(dataURL.split(",")[1]);
array = [];
i = 0;
len = binary.length;
while (i < len) {
array.push(binary.charCodeAt(i));
i++;
}
return new Blob([new Uint8Array(array)], {
type: "image/png",
});
}
var p = [];
var cols = 17;
var rows = 17;
var unit = 15; //px
var img;
var penselect = "pencil";
var scrollable = true;
function setup() {
var cnv = createCanvas(cols*unit+2, rows*unit+2);
cnv.parent("p5");
img = createGraphics(cols*unit+2, rows*unit+2);
document.querySelectorAll('.penselect').forEach(item => item.onclick = () => {
penselect = document.querySelector('input[name="penselect"]:checked').value;
});
//
for (var i = 0; i < cols*rows; i++) p.push(0);
document.querySelectorAll('.clear').forEach(item => item.onclick = () => {
for (let a = 0; a < p.length; a++) p[a] = 0;
});
}
function draw() {
//clear
clear();
//draw the grid
stroke(255);
strokeWeight(0.2);
for (var c = 0; c < cols; c++) {
for (var r = 0; r < rows; r++) {
noFill();
rect(c*unit+1, r*unit+1, unit, unit);
}
}
//pointer
fill(0, 255, 0);
strokeWeight(0);
//mouse way
circle(mouseX, mouseY, 10);
if (mouseIsPressed && mouseButton === LEFT) {
//find slot under the pointer
var mouseC = int(mouseX/unit);
var mouseR = int(mouseY/unit);
if (mouseC >= 0 && mouseC < cols && mouseR >= 0 && mouseR < rows) {
if (penselect == "pencil") p[mouseC*cols + mouseR] = 1;
else if (penselect == "erasor") p[mouseC*cols + mouseR] = 0;
}
}
//touch way
if (touches.length > 0) {
circle(touches[0].x, touches[0].y, 10);
//find slot under the pointer
var mouseC = int(touches[0].x/unit);
var mouseR = int(touches[0].y/unit);
if (mouseC >= 0 && mouseC < cols && mouseR >= 0 && mouseR < rows) {
// if (scrollable == true) {
// scrollable = false;
// // firefox browser @ my android phone -> url bar auto-hiding kills drawing experience.
// // --> https://stackoverflow.com/a/63221105
// document.body.style.marginTop = `-${window.pageYOffset}px`;
// document.body.style.position = 'fixed';
// document.body.style.overflowY = 'scroll';
// }
if (penselect == "pencil") p[mouseC*cols + mouseR] = 1;
else if (penselect == "erasor") p[mouseC*cols + mouseR] = 0;
} else {
// if (scrollable == false) {
// scrollable = true;
// // firefox browser @ my android phone -> url bar auto-hiding kills drawing experience.
// // --> https://stackoverflow.com/a/63221105
// document.body.style.position = '';
// document.body.style.overflowY = '';
// if (document.body.style.marginTop) {
// const scrollTop = -parseInt(document.body.style.marginTop, 10);
// document.body.style.marginTop = '';
// window.scrollTo(window.pageXOffset, scrollTop);
// }
// }
}
}
//draw img
image(img, 0, 0);
img.clear();
img.strokeWeight(0);
img.fill(255);
for (var c = 0; c < cols; c++) {
for (var r = 0; r < rows; r++) {
if (p[c*cols + r] == 1) {
img.rect(c*unit+1, r*unit+1, unit, unit);
}
}
}
}
function submitForm(event) {
//TODO : first check if there is a drawing or not. (pixels)
//
var submit = document.getElementById("submit");
submit.setAttribute('disabled', 'disabled');
select("html").style('cursor', 'progress');
//
var form = document.getElementById("form");
var fd = new FormData(form);
//
var unit = 45; //mask&override 'unit' -> for crispy png.
var img2 = createGraphics(cols*unit+2, rows*unit+2);
img2.clear();
img2.strokeWeight(0);
img2.fill(255);
for (var c = 0; c < cols; c++) {
for (var r = 0; r < rows; r++) {
if (p[c*cols + r] == 1) {
img2.rect(c*unit+1, r*unit+1, unit, unit);
}
}
}
var dataurl = img2.elt.toDataURL();
var pixels = dataURLtoBlob(dataurl);
fd.append("pixels", pixels, "pixels.png");
for(var pair of fd.entries()) {
console.log(pair[0]+ ', '+ pair[1]);
}
//
var request = new XMLHttpRequest();
request.open("POST", "/entry");
request.onload = () => {
alert('감사합니다!');
location.reload();
//location.assign("/submit");
}
request.send(fd);
//
event.preventDefault();
}
const form = document.getElementById('form');
form.addEventListener('submit', submitForm);
//// ---- for 'list' rendering ----
function del(that) {
console.log();
let text;
let pass = prompt("패스워드를 맞춰보세요!", "숫자2개");
if (/^\d{2}$/.test(pass)) {
var target = that.parentElement.parentElement.getAttribute('foldername');
const trydelete = async () => {
const response = await fetch('/delete/' + target + '/' + pass);
if (response.ok) {
const json = await response.json();
return Promise.resolve(json);
} else {
return Promise.reject('no response.');
}
}
trydelete().then((resp) => {
if (resp.result) {
alert("지우기 성공!");
location.reload();
} else {
alert("지우기 실패-");
}
}).catch(console.log);
} else {
alert("암호는 숫자2개...");
}
}
</script>
</body>
</html>