Add subscription floating action button with toggle functionality

- Implemented a floating action button (FAB) for newsletter subscription in the template.
- Added JavaScript to handle the toggle state of the FAB and close it on outside clicks or Escape key press.
- Created CSS styles for the FAB, including animations and responsive design.
- Added a Django template tag to return a random default cover image for the FAB.
- Integrated a form for email input and submission within the FAB.
This commit is contained in:
Warren Chen 2026-03-06 18:54:33 +09:00
parent 8a0621d1ce
commit 4679cc70ef
23 changed files with 536 additions and 103 deletions

View File

@ -0,0 +1,173 @@
.newsletter-page {
background: #0e1b42 url("../img/newsletter_bg.png") center center / cover no-repeat fixed;
}
.newsletter-page .site-main > .site-container {
padding-top: 64px;
padding-bottom: 64px;
min-height: 400px;
}
.newsletter-status {
max-width: 640px;
margin: 0 auto;
padding: 24px;
color: #ffffff;
}
.newsletter-status h1 {
font-size: 32px;
margin-top: 0;
}
.newsletter-status p {
margin: 0;
font-size: 20px;
line-height: 1.5;
}
.newsletter-status-message {
margin-bottom: 16px;
}
.newsletter-unsubscribe-form label {
display: block;
margin-bottom: 8px;
}
.newsletter-unsubscribe-form input[type="email"],
.newsletter-unsubscribe-form input[type="text"] {
width: 300px;
padding: 8px;
border: 0;
background: #ffffff4d;
color: #ffffff;
}
.newsletter-unsubscribe-form button {
width: 131px;
height: 49px;
border: 0;
background: #ffffff;
font-size: 14px;
font-weight: 500;
color: #000000;
padding: 10px 16px;
cursor: pointer;
margin-top: 12px;
}
.newsletter-back-link {
display: inline-flex;
align-items: center;
gap: 16px;
margin-top: 20px;
color: #ffffff;
text-decoration: none;
}
.newsletter-back-link__icon {
position: relative;
width: 52px;
height: 52px;
background: #ffffff80;
}
.newsletter-back-link__icon::before,
.newsletter-back-link__icon::after {
content: "";
position: absolute;
left: 18px;
width: 15px;
height: 1.5px;
background: #0e1b42;
}
.newsletter-back-link__icon::before {
top: 21px;
transform: rotate(-45deg);
}
.newsletter-back-link__icon::after {
top: 31px;
transform: rotate(45deg);
}
.newsletter-back-link__text {
font-size: 16px;
line-height: 1.2;
}
@media (min-width: 575px) and (max-width: 767px) {
.newsletter-page .site-main > .site-container {
min-height: 360px;
}
.newsletter-status {
padding: 16px;
}
.newsletter-status h1 {
font-size: 24px;
}
.newsletter-status p {
font-size: 16px;
}
.newsletter-unsubscribe-form input[type="email"],
.newsletter-unsubscribe-form input[type="text"] {
width: 240px;
}
}
@media (max-width: 574px) {
.newsletter-page .site-main > .site-container {
min-height: 320px;
}
.newsletter-status {
padding: 16px;
}
.newsletter-status h1 {
font-size: 20px;
}
.newsletter-status p {
font-size: 14px;
}
.newsletter-unsubscribe-form input[type="email"],
.newsletter-unsubscribe-form input[type="text"] {
width: 260px;
}
.newsletter-unsubscribe-form button {
width: 90px;
height: 31px;
padding: 4px 16px;
}
.newsletter-back-link__icon {
width: 40px;
height: 40px;
}
.newsletter-back-link__icon::before {
top: 15px;
}
.newsletter-back-link__icon::after {
top: 25px;
}
.newsletter-back-link__icon::before,
.newsletter-back-link__icon::after {
left: 12px;
}
.newsletter-back-link__text {
font-size: 14px;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -1,9 +1,19 @@
{% extends "base.html" %}
{% load static %}
{% block body_class %}template-darkbackground newsletter-page{% endblock %}
{% block extra_css %}
<link rel="stylesheet" type="text/css" href="{% static 'css/newsletter.css' %}">
{% endblock %}
{% block content %}
<section class="newsletter-status{% if success %} is-success{% else %} is-failure{% endif %}">
<h1>{{ title }}</h1>
<div class="newsletter-status-message">{{ message|safe }}</div>
<p><a href="/">回到首頁</a></p>
<a class="newsletter-back-link" href="/">
<span class="newsletter-back-link__icon" aria-hidden="true"></span>
<span class="newsletter-back-link__text">回到首頁</span>
</a>
</section>
{% endblock %}

View File

@ -1,14 +1,20 @@
{% extends "base.html" %}
{% load static %}
{% block body_class %}template-darkbackground newsletter-page{% endblock %}
{% block extra_css %}
<link rel="stylesheet" type="text/css" href="{% static 'css/newsletter.css' %}">
{% endblock %}
{% block content %}
<section class="newsletter-status{% if can_submit %} is-warning{% else %} is-failure{% endif %}">
<h1>取消訂閱</h1>
<h1>很遺憾聽到您取消電子報訂閱</h1>
<div class="newsletter-status-message">{{ intro_message|safe }}</div>
{% if can_submit %}
<form method="post" action="{% url 'newsletter_unsubscribe' %}" class="newsletter-unsubscribe-form">
{% csrf_token %}
<label for="{{ form.email.id_for_label }}">退訂 Email</label>
{{ form.email }}
{{ form.token }}
<button type="submit">確認退訂</button>
@ -17,6 +23,9 @@
<p>退訂連結已失效或缺少必要參數。</p>
{% endif %}
<p><a href="/">回到首頁</a></p>
<a class="newsletter-back-link" href="/">
<span class="newsletter-back-link__icon" aria-hidden="true"></span>
<span class="newsletter-back-link__text">回到首頁</span>
</a>
</section>
{% endblock %}

View File

@ -191,8 +191,8 @@ def newsletter_subscribe(request):
request,
"base/newsletter/status.html",
_build_context(
title="請前往信箱確認",
message="我們已寄出確認信,請點擊信中的連結完成訂閱。",
title="訂閱確認已送出!",
message="感謝您的訂閱<br>下一步,請前往訂閱的信箱收取確認信函<br><br>在信函中點擊連結",
success=True,
),
)
@ -209,7 +209,7 @@ def newsletter_confirm(request):
request,
"base/newsletter/status.html",
_build_context(
title="訂閱認失敗",
title="訂閱失敗",
message=template_settings.confirm_failure_template,
success=False,
),
@ -222,7 +222,7 @@ def newsletter_confirm(request):
request,
"base/newsletter/status.html",
_build_context(
title="訂閱認成功",
title="訂閱成功",
message=template_settings.confirm_success_template,
success=True,
),
@ -232,7 +232,7 @@ def newsletter_confirm(request):
request,
"base/newsletter/status.html",
_build_context(
title="訂閱認失敗",
title="訂閱失敗",
message=template_settings.confirm_failure_template,
success=False,
),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

View File

@ -1,5 +1,5 @@
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags static %}
{% load wagtailcore_tags wagtailimages_tags static home_tags %}
{% block extra_css %}
<link rel="stylesheet" type="text/css" href="{% static 'css/article_page.css' %}">
@ -89,7 +89,8 @@
{% image related.cover_image max-194x133 as related_cover %}
<img src="{{ related_cover.url }}" alt="{{ related.title }}">
{% else %}
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ related.title }}">
{% random_default_cover as default_cover %}
<img src="{{ default_cover }}" alt="{{ related.title }}">
{% endif %}
</a>
<p class="related-article-date">{{ related.date|date:"Y/m/d" }}</p>

View File

@ -1,4 +1,4 @@
{% load wagtailimages_tags static %}
{% load wagtailimages_tags static home_tags %}
<div class="site-hero-band full-bleed">
<div class="site-container">
@ -11,7 +11,8 @@
{% image first_article.cover_image max-410x293 as cover %}
<img src="{{ cover.url }}" alt="{{ first_article.title }}"/>
{% else %}
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ first_article.title }}"/>
{% random_default_cover as default_cover %}
<img src="{{ default_cover }}" alt="{{ first_article.title }}"/>
{% endif %}
</a>
</div>
@ -50,7 +51,8 @@
{% image article.cover_image max-410x218 as cover %}
<img src="{{ cover.url }}" alt="{{ article.title }}"/>
{% else %}
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ article.title }}"/>
{% random_default_cover as default_cover %}
<img src="{{ default_cover }}" alt="{{ article.title }}"/>
{% endif %}
</div>
<div class="article-content">
@ -61,4 +63,4 @@
</a>
{% endfor %}
{% endif %}
</div>
</div>

View File

@ -1,4 +1,4 @@
{% load wagtailimages_tags static %}
{% load wagtailimages_tags static home_tags %}
{% block extra_css %}
<link rel="stylesheet" type="text/css" href="{% static 'css/block_list.css' %}">
{% endblock %}
@ -13,7 +13,8 @@
{% image first_article.cover_image max-480x293 as cover %}
<img src="{{ cover.url }}" alt="{{ first_article.title }}"/>
{% else %}
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ first_article.title }}"/>
{% random_default_cover as default_cover %}
<img src="{{ default_cover }}" alt="{{ first_article.title }}"/>
{% endif %}
</a>
</div>

View File

@ -1,4 +1,4 @@
{% load wagtailimages_tags static %}
{% load wagtailimages_tags static home_tags %}
{% block extra_css %}
<link rel="stylesheet" type="text/css" href="{% static 'css/block_list_lower.css' %}">
{% endblock %}
@ -12,7 +12,8 @@
{% image article.cover_image max-194x133 as cover %}
<img src="{{ cover.url }}" alt="{{ article.title }}"/>
{% else %}
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ article.title }}"/>
{% random_default_cover as default_cover %}
<img src="{{ default_cover }}" alt="{{ article.title }}"/>
{% endif %}
</div>
<div><span class="article-title">{{ article.title }}</span></div>

View File

@ -1,4 +1,4 @@
{% load wagtailimages_tags static %}
{% load wagtailimages_tags static home_tags %}
<div class="horizontal-list-wrap" data-horizontal-list>
<button class="horizontal-list-arrow is-hidden" type="button" data-dir="left" aria-label="上一頁">
@ -15,7 +15,8 @@
{% image article.cover_image max-194x133 as cover %}
<img src="{{ cover.url }}" alt="{{ article.title }}" height="133" width="194"/>
{% else %}
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ article.title }}" height="133" width="194"/>
{% random_default_cover as default_cover %}
<img src="{{ default_cover }}" alt="{{ article.title }}" height="133" width="194"/>
{% endif %}
</div>
<div><span class="article-title">{{ article.title }}</span></div>

View File

@ -1,4 +1,4 @@
{% load wagtailimages_tags static %}
{% load wagtailimages_tags static home_tags %}
<div class="news-list-wrap" data-news-list>
<div class="news-hero">
@ -30,7 +30,8 @@
{% image first_article.cover_image max-480x293 as cover %}
<img src="{{ cover.url }}" alt="{{ first_article.title }}" height="293" width="480"/>
{% else %}
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ first_article.title }}" height="293" width="480"/>
{% random_default_cover as default_cover %}
<img src="{{ default_cover }}" alt="{{ first_article.title }}" height="293" width="480"/>
{% endif %}
</a>
</div>
@ -65,7 +66,8 @@
{% image article.cover_image max-194x133 as cover %}
<img src="{{ cover.url }}" alt="{{ article.title }}" height="133" width="194"/>
{% else %}
<img src="{% static 'img/default_cover.jpg' %}" alt="{{ article.title }}" height="133" width="194"/>
{% random_default_cover as default_cover %}
<img src="{{ default_cover }}" alt="{{ article.title }}" height="133" width="194"/>
{% endif %}
</div>
<div><span class="article-title">{{ article.title }}</span></div>

View File

@ -0,0 +1,16 @@
import random
from django import template
from django.templatetags.static import static
register = template.Library()
@register.simple_tag
def random_default_cover():
choices = (
"img/default_cover_1.png",
"img/default_cover_2.png",
"img/default_cover_3.png",
)
return static(random.choice(choices))

View File

@ -311,31 +311,6 @@ a {
color: #0e1b42;
}
.newsletter-status {
max-width: 640px;
margin: 48px auto;
padding: 24px;
border: 1px solid #0e1b4233;
border-radius: 8px;
}
.newsletter-status h1 {
margin-top: 0;
}
.newsletter-status-message {
margin-bottom: 16px;
}
.newsletter-unsubscribe-form button {
border: 0;
border-radius: 4px;
background: #0e1b42;
color: #ffffff;
padding: 10px 16px;
cursor: pointer;
}
@media (max-width: 1023px) {
.site-container {
max-width: 640px;

View File

@ -0,0 +1,116 @@
.subscribe-fab {
--fab-toggle-width: 59px;
position: fixed;
right: 16px;
top: 12%;
transform: translateY(-50%);
z-index: 1000;
min-height: 59px;
}
.subscribe-fab__toggle {
flex: 0 0 var(--fab-toggle-width);
width: var(--fab-toggle-width);
height: 59px;
margin: 0 0 0 10px;
border: 0;
background: transparent;
color: #ffffff66;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
.subscribe-fab__icon {
width: 32px;
height: 32px;
transform: scale(1.35);
transform-origin: center;
}
.subscribe-fab__text {
font-size: 10px;
font-weight: 500;
line-height: 1;
}
.subscribe-fab__panel {
position: absolute;
width: 378px;
right: 0;
top: 0;
display: flex;
align-items: center;
min-width: 368px;
height: 59px;
padding: 0 10px 0 0;
border-radius: 36px;
background: #323232;
border: 1px solid #ffffff80;
transform: translateX(calc(100% - var(--fab-toggle-width)));
transition: transform 0.25s ease, background-color 0.2s ease;
}
.subscribe-fab:hover .subscribe-fab__panel {
background: #767676;
}
.subscribe-fab.is-open .subscribe-fab__panel {
background: #767676;
transform: translateX(44px);
}
.subscribe-fab:hover .subscribe-fab__toggle,
.subscribe-fab.is-open .subscribe-fab__toggle {
color: #0e1b42;;
}
.subscribe-fab__form {
display: flex;
align-items: center;
gap: 8px;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s ease;
}
.subscribe-fab.is-open .subscribe-fab__form {
opacity: 1;
pointer-events: auto;
}
.subscribe-fab__input {
width: 190px;
height: 30px;
border: 0;
padding: 0 10px;
background: #ffffff80;
color: #0e1b42;
font-size: 12px;
}
.subscribe-fab__input::placeholder {
color: #0e1b4288;
}
.subscribe-fab__submit {
height: 30px;
border: 0;
padding: 0 10px;
background: #ffffffcc;
color: #0e1b42;
font-size: 12px;
cursor: pointer;
}
@media (max-width: 375px) {
.subscribe-fab__input {
width: 150px;
}
.subscribe-fab.is-open .subscribe-fab__panel {
transform: translateX(84px);
}
}

View File

@ -0,0 +1,29 @@
(function () {
const root = document.querySelector("[data-subscribe-fab]");
if (!root) return;
const toggle = root.querySelector(".subscribe-fab__toggle");
if (!toggle) return;
const setOpen = (open) => {
root.classList.toggle("is-open", open);
toggle.setAttribute("aria-expanded", open ? "true" : "false");
};
toggle.addEventListener("click", function () {
const isOpen = root.classList.contains("is-open");
setOpen(!isOpen);
});
document.addEventListener("click", function (event) {
if (!root.contains(event.target)) {
setOpen(false);
}
});
document.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
setOpen(false);
}
});
})();

View File

@ -35,6 +35,7 @@
{# Global stylesheets #}
<link rel="stylesheet" type="text/css" href="{% static 'css/mysite.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/footer.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/subscribe_fab.css' %}">
{% block extra_css %}
{# Override this in templates to add extra stylesheets #}
@ -53,10 +54,12 @@
</main>
{% include "includes/footer.html" %}
{% include "includes/subscribe_fab.html" %}
{# Global javascript #}
<script type="text/javascript" src="{% static 'js/mysite.js' %}"></script>
<script type="text/javascript" src="{% static 'js/header.js' %}"></script>
<script type="text/javascript" src="{% static 'js/subscribe_fab.js' %}"></script>
{# Instagram embed script to render IG oEmbeds #}
<script async src="https://www.instagram.com/embed.js"></script>

View File

@ -1,4 +1,4 @@
{% load navigation_tags %}
{% load navigation_tags wagtailcore_tags %}
<footer class="site-footer">
<div class="site-container footer-shell">
@ -20,12 +20,19 @@
<li><a href="#">最新文章</a></li>
{% endif %}
{% wagtail_site as current_site %}
{% if page %}
{% with site_root=page.get_site.root_page %}
{% for menu_page in site_root.get_children.live.in_menu %}
<li><a href="{{ menu_page.url }}">{{ menu_page.title }}</a></li>
{% endfor %}
{% endwith %}
{% elif current_site %}
{% with site_root=current_site.root_page %}
{% for menu_page in site_root.get_children.live.in_menu %}
<li><a href="{{ menu_page.url }}">{{ menu_page.title }}</a></li>
{% endfor %}
{% endwith %}
{% endif %}
</ul>
</nav>

View File

@ -1,4 +1,4 @@
{% load wagtailsettings_tags wagtailimages_tags %}
{% load wagtailsettings_tags wagtailimages_tags wagtailcore_tags %}
{% get_settings use_default_site=True as settings %}
<header class="site-header{% if settings.base.HeaderSettings.logo_light and settings.base.HeaderSettings.logo_dark %} has-logo-variants{% endif %}">
@ -39,67 +39,132 @@
<nav class="main-nav" id="site-nav">
<ul class="main-menu" id="main-menu">
{% with site_root=page.get_site.root_page %}
{# Top-level menu: direct children of site root #}
<li class="menu-item">
<div class="menu-item-header">
<a href="#">
<span class="main-menu-link">最新文章</span>
</a>
{% if nav_latest_page or nav_trending_page %}
<button class="submenu-toggle" type="button" aria-expanded="false" aria-label="Toggle submenu"></button>
{% endif %}
</div>
{% if nav_latest_page or nav_trending_page %}
<span class="menu-divider" aria-hidden="true"></span>
<ul class="submenu">
{% if nav_latest_page %}
<li class="submenu-item">
<a href="{{ nav_latest_page.url }}">
<span class="submenu-item-link">{{ nav_latest_page.title }}</span>
</a>
</li>
{% endif %}
{% if nav_trending_page %}
<li class="submenu-item">
<a href="{{ nav_trending_page.url }}">
<span class="submenu-item-link">{{ nav_trending_page.title }}</span>
</a>
</li>
{% endif %}
</ul>
{% endif %}
</li>
{% for menu_page in site_root.get_children.live.in_menu %}
{% wagtail_site as current_site %}
{% if page %}
{% with site_root=page.get_site.root_page %}
{# Top-level menu: direct children of site root #}
<li class="menu-item">
<div class="menu-item-header">
<a href="{{ menu_page.url }}">
<span class="main-menu-link">{{ menu_page.title }}</span>
<a href="#">
<span class="main-menu-link">最新文章</span>
</a>
{% if nav_latest_page or nav_trending_page %}
<button class="submenu-toggle" type="button" aria-expanded="false" aria-label="Toggle submenu"></button>
{% endif %}
</div>
{% if nav_latest_page or nav_trending_page %}
<span class="menu-divider" aria-hidden="true"></span>
<ul class="submenu">
{% if nav_latest_page %}
<li class="submenu-item">
<a href="{{ nav_latest_page.url }}">
<span class="submenu-item-link">{{ nav_latest_page.title }}</span>
</a>
</li>
{% endif %}
{% if nav_trending_page %}
<li class="submenu-item">
<a href="{{ nav_trending_page.url }}">
<span class="submenu-item-link">{{ nav_trending_page.title }}</span>
</a>
</li>
{% endif %}
</ul>
{% endif %}
</li>
{% for menu_page in site_root.get_children.live.in_menu %}
<li class="menu-item">
<div class="menu-item-header">
<a href="{{ menu_page.url }}">
<span class="main-menu-link">{{ menu_page.title }}</span>
</a>
{% with submenu=menu_page.get_children.live.in_menu %}
{% if submenu %}
<button class="submenu-toggle" type="button" aria-expanded="false" aria-label="Toggle submenu"></button>
{% endif %}
{% endwith %}
</div>
{# Second-level: direct children of each top-level page #}
{% with submenu=menu_page.get_children.live.in_menu %}
{% if submenu %}
<button class="submenu-toggle" type="button" aria-expanded="false" aria-label="Toggle submenu"></button>
<span class="menu-divider" aria-hidden="true"></span>
<ul class="submenu">
{% for subpage in submenu %}
<li class="submenu-item">
<a href="{{ subpage.url }}">
<span class="submenu-item-link">{{ subpage.title }}</span>
</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
</div>
{# Second-level: direct children of each top-level page #}
{% with submenu=menu_page.get_children.live.in_menu %}
{% if submenu %}
<span class="menu-divider" aria-hidden="true"></span>
<ul class="submenu">
{% for subpage in submenu %}
<li class="submenu-item">
<a href="{{ subpage.url }}">
<span class="submenu-item-link">{{ subpage.title }}</span>
</a>
</li>
{% endfor %}
</ul>
</li>
{% endfor %}
{% endwith %}
{% elif current_site %}
{% with site_root=current_site.root_page %}
{# Top-level menu: direct children of site root #}
<li class="menu-item">
<div class="menu-item-header">
<a href="#">
<span class="main-menu-link">最新文章</span>
</a>
{% if nav_latest_page or nav_trending_page %}
<button class="submenu-toggle" type="button" aria-expanded="false" aria-label="Toggle submenu"></button>
{% endif %}
{% endwith %}
</div>
{% if nav_latest_page or nav_trending_page %}
<span class="menu-divider" aria-hidden="true"></span>
<ul class="submenu">
{% if nav_latest_page %}
<li class="submenu-item">
<a href="{{ nav_latest_page.url }}">
<span class="submenu-item-link">{{ nav_latest_page.title }}</span>
</a>
</li>
{% endif %}
{% if nav_trending_page %}
<li class="submenu-item">
<a href="{{ nav_trending_page.url }}">
<span class="submenu-item-link">{{ nav_trending_page.title }}</span>
</a>
</li>
{% endif %}
</ul>
{% endif %}
</li>
{% endfor %}
{% endwith %}
{% for menu_page in site_root.get_children.live.in_menu %}
<li class="menu-item">
<div class="menu-item-header">
<a href="{{ menu_page.url }}">
<span class="main-menu-link">{{ menu_page.title }}</span>
</a>
{% with submenu=menu_page.get_children.live.in_menu %}
{% if submenu %}
<button class="submenu-toggle" type="button" aria-expanded="false" aria-label="Toggle submenu"></button>
{% endif %}
{% endwith %}
</div>
{# Second-level: direct children of each top-level page #}
{% with submenu=menu_page.get_children.live.in_menu %}
{% if submenu %}
<span class="menu-divider" aria-hidden="true"></span>
<ul class="submenu">
{% for subpage in submenu %}
<li class="submenu-item">
<a href="{{ subpage.url }}">
<span class="submenu-item-link">{{ subpage.title }}</span>
</a>
</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
</li>
{% endfor %}
{% endwith %}
{% endif %}
{# Optional extra links from settings #}
{% if settings.base.HeaderSettings.main_links %}

View File

@ -0,0 +1,22 @@
<div class="subscribe-fab" data-subscribe-fab>
<div id="subscribe-fab-panel" class="subscribe-fab__panel">
<button class="subscribe-fab__toggle" type="button" aria-expanded="false" aria-controls="subscribe-fab-panel">
<svg class="subscribe-fab__icon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M3 6h18v12H3z" fill="none" stroke="currentColor" stroke-width="0.5"/>
<path d="M3 7l9 7 9-7" fill="none" stroke="currentColor" stroke-width="0.5"/>
</svg>
<span class="subscribe-fab__text">點我訂閱</span>
</button>
<form class="subscribe-fab__form" method="post" action="{% url 'newsletter_subscribe' %}">
{% csrf_token %}
<input
class="subscribe-fab__input"
type="email"
name="email"
required
placeholder="輸入 Email">
<button class="subscribe-fab__submit" type="submit">確認訂閱</button>
</form>
</div>
</div>