Add post visibility controls and island profile avatar
This commit is contained in:
parent
1fcff38e56
commit
80b266ca56
|
|
@ -229,6 +229,24 @@ export async function fetchPostById(postId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updatePostVisibility(postId, payload = {}, token) {
|
||||||
|
if (!postId) throw new Error("post_id required");
|
||||||
|
const headers = { "Content-Type": "application/json" };
|
||||||
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
||||||
|
const body = JSON.stringify({ post_id: postId, ...payload });
|
||||||
|
const res = await fetch(`${API_BASE}/post/visibility`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers,
|
||||||
|
credentials: "include",
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const text = await res.text().catch(() => "");
|
||||||
|
throw new Error(text || `HTTP ${res.status}`);
|
||||||
|
}
|
||||||
|
return res.json().catch(() => ({}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import React, { useEffect, useRef } from 'react';
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
import maplibregl from 'maplibre-gl';
|
import maplibregl from 'maplibre-gl';
|
||||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||||
import '../../styles/island.css';
|
import '../../styles/island.css';
|
||||||
|
import { uploadProfileAvatar } from '../../api/client';
|
||||||
|
import { useAuth } from '../../auth/AuthContext';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IslandViewer: 2.5D immersive island visualization using MapLibre
|
* IslandViewer: 2.5D immersive island visualization using MapLibre
|
||||||
|
|
@ -11,6 +13,12 @@ export default function IslandViewer({ island, onClose }) {
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const mapRef = useRef(null);
|
const mapRef = useRef(null);
|
||||||
const animationRef = useRef(null);
|
const animationRef = useRef(null);
|
||||||
|
const fileRef = useRef(null);
|
||||||
|
const { authenticated, username, setAvatarUrl } = useAuth();
|
||||||
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const [avatarVisibility, setAvatarVisibility] = useState("public");
|
||||||
|
const [avatarGroupId, setAvatarGroupId] = useState("");
|
||||||
|
const [avatarUsers, setAvatarUsers] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!island || !containerRef.current) return;
|
if (!island || !containerRef.current) return;
|
||||||
|
|
@ -131,6 +139,9 @@ export default function IslandViewer({ island, onClose }) {
|
||||||
}, [island]);
|
}, [island]);
|
||||||
|
|
||||||
if (!island) return null;
|
if (!island) return null;
|
||||||
|
const isSelf =
|
||||||
|
authenticated && username && island.username &&
|
||||||
|
username.toLowerCase() === island.username.toLowerCase();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="island-viewer-modal" onClick={onClose}>
|
<div className="island-viewer-modal" onClick={onClose}>
|
||||||
|
|
@ -142,6 +153,91 @@ export default function IslandViewer({ island, onClose }) {
|
||||||
<p>{island.bio || 'Welcome to my island'}</p>
|
<p>{island.bio || 'Welcome to my island'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="island-profile-panel">
|
||||||
|
<div className="island-profile-avatar">
|
||||||
|
{island.avatar_url ? (
|
||||||
|
<img src={island.avatar_url} alt={island.username} />
|
||||||
|
) : (
|
||||||
|
<div className="island-profile-letter">
|
||||||
|
{(island.username || "U")[0].toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="island-profile-meta">
|
||||||
|
<div className="island-profile-name">@{island.username}</div>
|
||||||
|
{isSelf ? (
|
||||||
|
<div className="island-profile-actions">
|
||||||
|
<div className="island-profile-row">
|
||||||
|
<label>Photo visibility</label>
|
||||||
|
<select
|
||||||
|
value={avatarVisibility}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = e.target.value;
|
||||||
|
setAvatarVisibility(next);
|
||||||
|
if (next !== "group") setAvatarGroupId("");
|
||||||
|
if (next !== "custom") setAvatarUsers("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="public">Public</option>
|
||||||
|
<option value="private">Private</option>
|
||||||
|
<option value="group">Group</option>
|
||||||
|
<option value="custom">Custom</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{avatarVisibility === "group" ? (
|
||||||
|
<input
|
||||||
|
className="island-profile-input"
|
||||||
|
placeholder="Group ID"
|
||||||
|
value={avatarGroupId}
|
||||||
|
onChange={(e) => setAvatarGroupId(e.target.value)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{avatarVisibility === "custom" ? (
|
||||||
|
<input
|
||||||
|
className="island-profile-input"
|
||||||
|
placeholder="Usernames (comma separated)"
|
||||||
|
value={avatarUsers}
|
||||||
|
onChange={(e) => setAvatarUsers(e.target.value)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="island-profile-btn"
|
||||||
|
disabled={uploading}
|
||||||
|
onClick={() => fileRef.current && fileRef.current.click()}
|
||||||
|
>
|
||||||
|
{uploading ? "Uploading..." : "Change photo"}
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
ref={fileRef}
|
||||||
|
type="file"
|
||||||
|
accept="image/*"
|
||||||
|
style={{ display: "none" }}
|
||||||
|
onChange={async (e) => {
|
||||||
|
const file = e.target.files && e.target.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
setUploading(true);
|
||||||
|
const users = avatarUsers
|
||||||
|
.split(",")
|
||||||
|
.map((u) => u.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const res = await uploadProfileAvatar(file, {
|
||||||
|
visibility: avatarVisibility,
|
||||||
|
group_id: avatarGroupId.trim(),
|
||||||
|
users,
|
||||||
|
});
|
||||||
|
if (res && res.ok && res.avatar_url) {
|
||||||
|
island.avatar_url = res.avatar_url;
|
||||||
|
if (setAvatarUrl) setAvatarUrl(res.avatar_url);
|
||||||
|
}
|
||||||
|
setUploading(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="island-viewer-canvas"
|
className="island-viewer-canvas"
|
||||||
|
|
@ -402,4 +498,3 @@ function seededRandom(seed) {
|
||||||
return s / 233280;
|
return s / 233280;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,6 +121,9 @@ export default function MapView({
|
||||||
const [draftImageFile, setDraftImageFile] = useState(null);
|
const [draftImageFile, setDraftImageFile] = useState(null);
|
||||||
const [draftImagePreview, setDraftImagePreview] = useState("");
|
const [draftImagePreview, setDraftImagePreview] = useState("");
|
||||||
const [useCustomImage, setUseCustomImage] = useState(false);
|
const [useCustomImage, setUseCustomImage] = useState(false);
|
||||||
|
const [postVisibility, setPostVisibility] = useState("public");
|
||||||
|
const [postGroupId, setPostGroupId] = useState("");
|
||||||
|
const [postUsers, setPostUsers] = useState("");
|
||||||
const [imageVisibility, setImageVisibility] = useState("public");
|
const [imageVisibility, setImageVisibility] = useState("public");
|
||||||
const [imageGroupId, setImageGroupId] = useState("");
|
const [imageGroupId, setImageGroupId] = useState("");
|
||||||
const [imageUsers, setImageUsers] = useState("");
|
const [imageUsers, setImageUsers] = useState("");
|
||||||
|
|
@ -496,6 +499,9 @@ export default function MapView({
|
||||||
setDraftImageFile(null);
|
setDraftImageFile(null);
|
||||||
setDraftImagePreview("");
|
setDraftImagePreview("");
|
||||||
setUseCustomImage(false);
|
setUseCustomImage(false);
|
||||||
|
setPostVisibility("public");
|
||||||
|
setPostGroupId("");
|
||||||
|
setPostUsers("");
|
||||||
setImageVisibility("public");
|
setImageVisibility("public");
|
||||||
setImageGroupId("");
|
setImageGroupId("");
|
||||||
setImageUsers("");
|
setImageUsers("");
|
||||||
|
|
@ -640,6 +646,12 @@ export default function MapView({
|
||||||
lat,
|
lat,
|
||||||
lon: lng,
|
lon: lng,
|
||||||
author: authorName,
|
author: authorName,
|
||||||
|
visibility: postVisibility,
|
||||||
|
group_id: postGroupId.trim(),
|
||||||
|
users: postUsers
|
||||||
|
.split(",")
|
||||||
|
.map((u) => u.trim())
|
||||||
|
.filter(Boolean),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (useCustomImage && draftImage.trim()) {
|
if (useCustomImage && draftImage.trim()) {
|
||||||
|
|
@ -816,6 +828,40 @@ export default function MapView({
|
||||||
onChange={(e) => setDraftBody(e.target.value)}
|
onChange={(e) => setDraftBody(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="create-privacy-row">
|
||||||
|
<label>Post visibility</label>
|
||||||
|
<select
|
||||||
|
value={postVisibility}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = e.target.value;
|
||||||
|
setPostVisibility(next);
|
||||||
|
if (next !== "group") setPostGroupId("");
|
||||||
|
if (next !== "custom") setPostUsers("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="public">Public</option>
|
||||||
|
<option value="private">Private</option>
|
||||||
|
<option value="group">Group</option>
|
||||||
|
<option value="custom">Custom</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{postVisibility === "group" ? (
|
||||||
|
<input
|
||||||
|
className="create-input"
|
||||||
|
placeholder="Group ID"
|
||||||
|
value={postGroupId}
|
||||||
|
onChange={(e) => setPostGroupId(e.target.value)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{postVisibility === "custom" ? (
|
||||||
|
<input
|
||||||
|
className="create-input"
|
||||||
|
placeholder="Usernames (comma separated)"
|
||||||
|
value={postUsers}
|
||||||
|
onChange={(e) => setPostUsers(e.target.value)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div className="create-image-option">
|
<div className="create-image-option">
|
||||||
<label style={{ display: "flex", alignItems: "center", gap: "8px", cursor: "pointer" }}>
|
<label style={{ display: "flex", alignItems: "center", gap: "8px", cursor: "pointer" }}>
|
||||||
<input
|
<input
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ import {
|
||||||
fetchPostLikeState,
|
fetchPostLikeState,
|
||||||
recordPostShare,
|
recordPostShare,
|
||||||
togglePostLike,
|
togglePostLike,
|
||||||
|
updatePostVisibility,
|
||||||
} from "../../api/client";
|
} from "../../api/client";
|
||||||
|
import { useAuth } from "../../auth/AuthContext";
|
||||||
|
|
||||||
function isAuthed() {
|
function isAuthed() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -105,11 +107,23 @@ export default function PostCard({ post, selected, onSelect }) {
|
||||||
const [liked, setLiked] = useState(false);
|
const [liked, setLiked] = useState(false);
|
||||||
const [authed, setAuthed] = useState(isAuthed());
|
const [authed, setAuthed] = useState(isAuthed());
|
||||||
const [imgFailed, setImgFailed] = useState(false);
|
const [imgFailed, setImgFailed] = useState(false);
|
||||||
|
const [showPrivacy, setShowPrivacy] = useState(false);
|
||||||
|
const [privacySaving, setPrivacySaving] = useState(false);
|
||||||
|
const [privacyError, setPrivacyError] = useState("");
|
||||||
|
const [postVisibility, setPostVisibility] = useState(
|
||||||
|
(post?.visibility || "public").toString()
|
||||||
|
);
|
||||||
|
const [postGroupId, setPostGroupId] = useState(
|
||||||
|
(post?.group_id || "").toString()
|
||||||
|
);
|
||||||
|
const [postUsers, setPostUsers] = useState("");
|
||||||
|
const [applyToImages, setApplyToImages] = useState(true);
|
||||||
const [counts, setCounts] = useState(() => ({
|
const [counts, setCounts] = useState(() => ({
|
||||||
like: post?.like_count ?? 0,
|
like: post?.like_count ?? 0,
|
||||||
comment: post?.comment_count ?? 0,
|
comment: post?.comment_count ?? 0,
|
||||||
share: post?.share_count ?? 0,
|
share: post?.share_count ?? 0,
|
||||||
}));
|
}));
|
||||||
|
const { username, token } = useAuth();
|
||||||
|
|
||||||
const postId = post?.id ?? post?._id ?? 0;
|
const postId = post?.id ?? post?._id ?? 0;
|
||||||
|
|
||||||
|
|
@ -142,12 +156,18 @@ export default function PostCard({ post, selected, onSelect }) {
|
||||||
share: post?.share_count ?? 0,
|
share: post?.share_count ?? 0,
|
||||||
});
|
});
|
||||||
}, [post?.like_count, post?.comment_count, post?.share_count]);
|
}, [post?.like_count, post?.comment_count, post?.share_count]);
|
||||||
|
useEffect(() => {
|
||||||
|
setPostVisibility((post?.visibility || "public").toString());
|
||||||
|
setPostGroupId((post?.group_id || "").toString());
|
||||||
|
}, [post?.visibility, post?.group_id]);
|
||||||
|
|
||||||
const rawImg = post?.image_large || post?.image_small || post?.image || post?.media_url || "";
|
const rawImg = post?.image_large || post?.image_small || post?.image || post?.media_url || "";
|
||||||
const img = normalizeImageUrl(rawImg);
|
const img = normalizeImageUrl(rawImg);
|
||||||
const title = post?.title || "Untitled";
|
const title = post?.title || "Untitled";
|
||||||
const snippet = post?.snippet || "";
|
const snippet = post?.snippet || "";
|
||||||
const author = (post?.author || post?.Author || "anon").toString();
|
const author = (post?.author || post?.Author || "anon").toString();
|
||||||
|
const isAuthor =
|
||||||
|
username && author && author.toLowerCase() === username.toLowerCase();
|
||||||
const sub = (post?.sub_category || post?.subCategory || "").toString();
|
const sub = (post?.sub_category || post?.subCategory || "").toString();
|
||||||
const timeAgo = formatRelativeTime(post?.created_at || post?.CreatedAt || post?.createdAt);
|
const timeAgo = formatRelativeTime(post?.created_at || post?.CreatedAt || post?.createdAt);
|
||||||
const cat = categoryLabel(post);
|
const cat = categoryLabel(post);
|
||||||
|
|
@ -264,7 +284,98 @@ export default function PostCard({ post, selected, onSelect }) {
|
||||||
>
|
>
|
||||||
<i className="fa-solid fa-share-nodes" /> Share
|
<i className="fa-solid fa-share-nodes" /> Share
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{isAuthor ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={"sw-wall-btn" + (showPrivacy ? " is-active" : "")}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowPrivacy((prev) => !prev);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<i className="fa-solid fa-lock" /> Visibility
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isAuthor && showPrivacy ? (
|
||||||
|
<div className="sw-wall-privacy" onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className="sw-wall-privacy-row">
|
||||||
|
<label>Post</label>
|
||||||
|
<select
|
||||||
|
value={postVisibility}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = e.target.value;
|
||||||
|
setPostVisibility(next);
|
||||||
|
if (next !== "group") setPostGroupId("");
|
||||||
|
if (next !== "custom") setPostUsers("");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="public">Public</option>
|
||||||
|
<option value="private">Private</option>
|
||||||
|
<option value="group">Group</option>
|
||||||
|
<option value="custom">Custom</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{postVisibility === "group" ? (
|
||||||
|
<input
|
||||||
|
className="sw-wall-privacy-input"
|
||||||
|
placeholder="Group ID"
|
||||||
|
value={postGroupId}
|
||||||
|
onChange={(e) => setPostGroupId(e.target.value)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{postVisibility === "custom" ? (
|
||||||
|
<input
|
||||||
|
className="sw-wall-privacy-input"
|
||||||
|
placeholder="Usernames (comma separated)"
|
||||||
|
value={postUsers}
|
||||||
|
onChange={(e) => setPostUsers(e.target.value)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
<label className="sw-wall-privacy-check">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={applyToImages}
|
||||||
|
onChange={(e) => setApplyToImages(e.target.checked)}
|
||||||
|
/>
|
||||||
|
Apply same visibility to images
|
||||||
|
</label>
|
||||||
|
{privacyError ? <div className="sw-wall-privacy-error">{privacyError}</div> : null}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="sw-wall-btn"
|
||||||
|
disabled={privacySaving}
|
||||||
|
onClick={async () => {
|
||||||
|
setPrivacyError("");
|
||||||
|
setPrivacySaving(true);
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
visibility: postVisibility,
|
||||||
|
group_id: postGroupId.trim(),
|
||||||
|
users: postUsers
|
||||||
|
.split(",")
|
||||||
|
.map((u) => u.trim())
|
||||||
|
.filter(Boolean),
|
||||||
|
};
|
||||||
|
if (applyToImages) {
|
||||||
|
payload.image_visibility = postVisibility;
|
||||||
|
payload.image_group_id = postGroupId.trim();
|
||||||
|
payload.image_users = payload.users;
|
||||||
|
}
|
||||||
|
await updatePostVisibility(postId, payload, token);
|
||||||
|
setPrivacySaving(false);
|
||||||
|
} catch (err) {
|
||||||
|
setPrivacyError("Failed to update visibility.");
|
||||||
|
setPrivacySaving(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{privacySaving ? "Saving..." : "Save"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</article>
|
</article>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,94 @@
|
||||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Profile panel */
|
||||||
|
.island-profile-panel {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||||
|
background: rgba(15, 23, 42, 0.6);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
.island-profile-avatar {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 999px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(56, 189, 248, 0.6);
|
||||||
|
background: rgba(56, 189, 248, 0.2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.island-profile-avatar img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.island-profile-letter {
|
||||||
|
color: #e2f2ff;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
.island-profile-meta {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.island-profile-name {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #e2e8f0;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
}
|
||||||
|
.island-profile-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
.island-profile-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #cbd5f5;
|
||||||
|
}
|
||||||
|
.island-profile-row select {
|
||||||
|
flex: 1;
|
||||||
|
background: rgba(15, 23, 42, 0.6);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.4);
|
||||||
|
color: #e2e8f0;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.2rem 0.6rem;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
.island-profile-input {
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(15, 23, 42, 0.6);
|
||||||
|
border: 1px solid rgba(148, 163, 184, 0.4);
|
||||||
|
color: #e2e8f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0.35rem 0.6rem;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
}
|
||||||
|
.island-profile-btn {
|
||||||
|
align-self: flex-start;
|
||||||
|
border: 1px solid rgba(56, 189, 248, 0.6);
|
||||||
|
background: rgba(56, 189, 248, 0.2);
|
||||||
|
color: #e2f2ff;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
padding: 0.35rem 0.8rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.island-profile-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
/* Canvas */
|
/* Canvas */
|
||||||
|
|
||||||
.island-viewer-canvas {
|
.island-viewer-canvas {
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,55 @@ body[data-theme="light"] .sw-wall-image-fallback{
|
||||||
gap:.4rem;
|
gap:.4rem;
|
||||||
flex-wrap:wrap;
|
flex-wrap:wrap;
|
||||||
}
|
}
|
||||||
|
.sw-wall-privacy{
|
||||||
|
margin-top:.25rem;
|
||||||
|
padding:.5rem .6rem;
|
||||||
|
border-radius:12px;
|
||||||
|
border:1px solid rgba(148,163,184,0.35);
|
||||||
|
background: rgba(15,23,42,0.75);
|
||||||
|
display:flex;
|
||||||
|
flex-direction:column;
|
||||||
|
gap:.4rem;
|
||||||
|
}
|
||||||
|
.sw-wall-privacy-row{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
gap:.5rem;
|
||||||
|
font-size:.72rem;
|
||||||
|
color:#cbd5f5;
|
||||||
|
}
|
||||||
|
.sw-wall-privacy-row label{
|
||||||
|
white-space:nowrap;
|
||||||
|
}
|
||||||
|
.sw-wall-privacy-row select{
|
||||||
|
flex:1;
|
||||||
|
background:#020617;
|
||||||
|
border-radius:999px;
|
||||||
|
border:1px solid #4b5563;
|
||||||
|
padding:.25rem .6rem;
|
||||||
|
font-size:.72rem;
|
||||||
|
color:#e5e7eb;
|
||||||
|
}
|
||||||
|
.sw-wall-privacy-input{
|
||||||
|
width:100%;
|
||||||
|
border-radius:999px;
|
||||||
|
border:1px solid #4b5563;
|
||||||
|
padding:.3rem .6rem;
|
||||||
|
background:#020617;
|
||||||
|
color:#e5e7eb;
|
||||||
|
font-size:.72rem;
|
||||||
|
}
|
||||||
|
.sw-wall-privacy-check{
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
gap:.45rem;
|
||||||
|
font-size:.7rem;
|
||||||
|
color:#cbd5f5;
|
||||||
|
}
|
||||||
|
.sw-wall-privacy-error{
|
||||||
|
font-size:.7rem;
|
||||||
|
color:#fca5a5;
|
||||||
|
}
|
||||||
|
|
||||||
.sw-wall-sentinel{
|
.sw-wall-sentinel{
|
||||||
height: 1px;
|
height: 1px;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue