← 一覧へ

Accordion / 028 — サイバーパンク

デザイン見本

  • Neon and Glitch Aesthetic
  • Clip-path Angled Cuts
  • Cyberpunk UI Elements

ネオンカラー(シアン・マゼンタ)、斜めに切り取られた角(clip-path)、スキャンラインを意識したサイバーパンク風アコーディオンデザインです。

実装コード

HTML
<div class="container">
    <div class="btn-box">
        <button data-default-text="ACCESS_DENIED // SYNC" data-open-text="ACCESS_GRANTED // OPEN">ACCESS_DENIED // SYNC</button>
    </div>
    <div class="more">
        <ul>
            <li>Neon and Glitch Aesthetic</li>
            <li>Clip-path Angled Cuts</li>
            <li>Cyberpunk UI Elements</li>
        </ul>
    </div>
</div>
CSS
@keyframes acc-glitch {
    0% { clip-path: polygon(0 2%, 100% 2%, 100% 5%, 0 5%); transform: translate(0); }
    20% { clip-path: polygon(0 15%, 100% 15%, 100% 15%, 0 15%); transform: translate(-2px, 2px); }
    40% { clip-path: polygon(0 10%, 100% 10%, 100% 20%, 0 20%); transform: translate(2px, -2px); }
    60% { clip-path: polygon(0 40%, 100% 40%, 100% 43%, 0 43%); transform: translate(0); }
    80% { clip-path: polygon(0 80%, 100% 80%, 100% 80%, 0 80%); transform: translate(2px, 2px); }
    100% { clip-path: polygon(0 0, 0 0, 0 0, 0 0); transform: translate(0); }
}

.container {
    margin-bottom: 0;
    max-width: 100%;
    background: #0d0d1a;
    padding: 16px;
    border-radius: 4px;
    position: relative;
    overflow: hidden;
}

.container::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background: linear-gradient(90deg, rgba(0, 255, 255, 0.03) 1px, transparent 1px) 0 0 / 20px 20px,
                linear-gradient(rgba(0, 255, 255, 0.03) 1px, transparent 1px) 0 0 / 20px 20px;
    pointer-events: none;
}

.container .btn-box button {
    width: 100%;
    padding: 16px 24px;
    background: linear-gradient(45deg, #0d0d1a, #1a1a2e);
    border: 1px solid #0ff;
    border-left: 4px solid #f0f;
    font-size: 14px;
    font-weight: 700;
    font-family: 'Courier New', Courier, monospace;
    letter-spacing: 2px;
    color: #0ff;
    cursor: pointer;
    transition: all 0.3s ease;
    text-align: left;
    position: relative;
    clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), calc(100% - 10px) 100%, 0 100%);
    box-shadow: inset 0 0 10px rgba(0, 255, 255, 0.1);
}

.container .btn-box button::after {
    content: '[+]';
    position: absolute;
    right: 20px;
    top: 50%;
    transform: translateY(-50%);
    color: #f0f;
    font-size: 16px;
    transition: content 0.3s;
}

.container:has(.more.appear) .btn-box button::after {
    content: '[-]';
    color: #0ff;
}

.container .btn-box button:hover {
    background: rgba(0, 255, 255, 0.1);
    box-shadow: inset 0 0 15px rgba(0, 255, 255, 0.3), 0 0 10px rgba(0, 255, 255, 0.2);
    text-shadow: 0 0 5px #0ff;
}

.container .more {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.4s cubic-bezier(0.1, 0.8, 0.2, 1);
    background: rgba(13, 13, 26, 0.8);
    border: 1px solid #0ff;
    border-top: none;
    border-right: 4px solid #f0f;
    margin-top: 0;
    position: relative;
    clip-path: polygon(0 0, 100% 0, 100% 100%, 10px 100%, 0 calc(100% - 10px));
}

.container .more::before {
    content: '';
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background: repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0, 255, 255, 0.05) 2px, rgba(0, 255, 255, 0.05) 4px);
    pointer-events: none;
}

.container .more.appear {
    max-height: 300px;
}

.container .more ul {
    list-style: none;
    padding: 20px;
    margin: 0;
    position: relative;
    z-index: 1;
}

.container .more li {
    padding: 10px 0;
    color: #e0e0e0;
    border-bottom: 1px dashed rgba(0, 255, 255, 0.3);
    font-size: 13px;
    font-family: 'Courier New', Courier, monospace;
}

.container .more li::before {
    content: '> ';
    color: #f0f;
}

.container .more li:last-child {
    border-bottom: none;
}
JS
(function () {
    var container = document.querySelector('.container');
    if (!container) return;
    var button = container.querySelector('.btn-box button');
    var content = container.querySelector('.more');
    
    if (button && content) {
        button.addEventListener('click', function() {
            content.classList.toggle('appear');
            
            if (content.classList.contains('appear')) {
                this.textContent = this.dataset.openText || 'Close';
            } else {
                this.textContent = this.dataset.defaultText;
            }
        });
    }
})();