brand: update app icon, iOS assets, Android drawables, brand docs (Pass H)
|
|
@ -56,7 +56,7 @@ object NotificationHelper {
|
|||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val notification = NotificationCompat.Builder(context, channelId)
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||
.setSmallIcon(R.drawable.ic_notification_closer)
|
||||
.setContentTitle(title)
|
||||
.setContentText(body)
|
||||
.setAutoCancel(true)
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ class PartnerNotificationManager @Inject constructor(
|
|||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(context, type.channelId)
|
||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
||||
.setSmallIcon(R.drawable.ic_notification_closer)
|
||||
.setContentTitle(type.title)
|
||||
.setContentText(type.body)
|
||||
.setAutoCancel(true)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package app.closer.ui.components
|
||||
|
||||
import app.closer.R
|
||||
import app.closer.ui.theme.closerCardColor
|
||||
import android.provider.Settings
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
|
|
@ -8,7 +9,7 @@ import androidx.compose.animation.core.animateFloat
|
|||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -20,13 +21,9 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||
import androidx.compose.ui.graphics.drawscope.withTransform
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.graphics.vector.PathParser
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
|
|
@ -67,6 +64,14 @@ fun LoadingState(
|
|||
fun CloserHeartLoader(
|
||||
modifier: Modifier = Modifier,
|
||||
size: Dp = 76.dp
|
||||
) {
|
||||
CloserMarkLoader(modifier = modifier, size = size)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CloserMarkLoader(
|
||||
modifier: Modifier = Modifier,
|
||||
size: Dp = 76.dp
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val reducedMotion = remember {
|
||||
|
|
@ -76,16 +81,7 @@ fun CloserHeartLoader(
|
|||
1f
|
||||
) == 0f
|
||||
}
|
||||
val transition = rememberInfiniteTransition(label = "closerHeartLoader")
|
||||
val animatedFill = transition.animateFloat(
|
||||
initialValue = 0.08f,
|
||||
targetValue = 1f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(durationMillis = 1500, easing = FastOutSlowInEasing),
|
||||
repeatMode = RepeatMode.Restart
|
||||
),
|
||||
label = "closerHeartFill"
|
||||
)
|
||||
val transition = rememberInfiniteTransition(label = "closerMarkLoader")
|
||||
val animatedPulse = transition.animateFloat(
|
||||
initialValue = 0.96f,
|
||||
targetValue = 1.04f,
|
||||
|
|
@ -93,38 +89,13 @@ fun CloserHeartLoader(
|
|||
animation = tween(durationMillis = 900, easing = FastOutSlowInEasing),
|
||||
repeatMode = RepeatMode.Reverse
|
||||
),
|
||||
label = "closerHeartPulse"
|
||||
label = "closerMarkPulse"
|
||||
)
|
||||
|
||||
val fillProgress = if (reducedMotion) 1f else animatedFill.value
|
||||
val pulse = if (reducedMotion) 1f else animatedPulse.value
|
||||
val shadowPath = remember {
|
||||
PathParser().parsePathString(
|
||||
"M54,89C48,82 25,65 20,50C15,35 23,22 37,22C45,22 51,26 54,33C57,26 63,22 71,22C85,22 93,35 88,50C83,65 60,82 54,89Z"
|
||||
).toPath()
|
||||
}
|
||||
val leftPath = remember {
|
||||
PathParser().parsePathString(
|
||||
"M54,85C49,79 27,62 22,48C17,35 24,24 37,24C45,24 51,28 54,35Z"
|
||||
).toPath()
|
||||
}
|
||||
val rightPath = remember {
|
||||
PathParser().parsePathString(
|
||||
"M54,85C59,79 81,62 86,48C91,35 84,24 71,24C63,24 57,28 54,35Z"
|
||||
).toPath()
|
||||
}
|
||||
val leftHighlight = remember {
|
||||
PathParser().parsePathString(
|
||||
"M27,42C28,32 34,27 42,27C48,27 52,30 54,35L54,41C47,36 37,36 27,42Z"
|
||||
).toPath()
|
||||
}
|
||||
val rightHighlight = remember {
|
||||
PathParser().parsePathString(
|
||||
"M54,35C57,30 62,27 69,27C78,27 84,32 85,42C75,36 65,36 54,41Z"
|
||||
).toPath()
|
||||
}
|
||||
|
||||
Canvas(
|
||||
Image(
|
||||
painter = painterResource(R.drawable.closer_mark_loader),
|
||||
contentDescription = null,
|
||||
modifier = modifier
|
||||
.size(size)
|
||||
.graphicsLayer {
|
||||
|
|
@ -132,22 +103,5 @@ fun CloserHeartLoader(
|
|||
scaleY = pulse
|
||||
}
|
||||
.clearAndSetSemantics {}
|
||||
) {
|
||||
val scaleX = this.size.width / 108f
|
||||
val scaleY = this.size.height / 108f
|
||||
|
||||
withTransform({
|
||||
scale(scaleX = scaleX, scaleY = scaleY, pivot = Offset.Zero)
|
||||
}) {
|
||||
drawPath(shadowPath, color = Color(0xFF24122F).copy(alpha = 0.10f))
|
||||
drawPath(leftPath, color = Color(0xFFF7C8E4).copy(alpha = 0.22f))
|
||||
drawPath(rightPath, color = Color(0xFFD9B8FF).copy(alpha = 0.22f))
|
||||
clipRect(top = 108f * (1f - fillProgress), bottom = 108f) {
|
||||
drawPath(leftPath, color = Color(0xFFF7C8E4))
|
||||
drawPath(rightPath, color = Color(0xFFD9B8FF))
|
||||
drawPath(leftHighlight, color = Color(0xFFFFF4FA).copy(alpha = 0.68f))
|
||||
drawPath(rightHighlight, color = Color(0xFFF3E8FF).copy(alpha = 0.52f))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -1,33 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#24122F"
|
||||
android:fillAlpha="0.38"
|
||||
android:pathData="M54,89C48,82 25,65 20,50C15,35 23,22 37,22C45,22 51,26 54,33C57,26 63,22 71,22C85,22 93,35 88,50C83,65 60,82 54,89Z" />
|
||||
<path
|
||||
android:fillColor="#F7C8E4"
|
||||
android:pathData="M54,85C49,79 27,62 22,48C17,35 24,24 37,24C45,24 51,28 54,35Z" />
|
||||
<path
|
||||
android:fillColor="#D9B8FF"
|
||||
android:pathData="M54,85C59,79 81,62 86,48C91,35 84,24 71,24C63,24 57,28 54,35Z" />
|
||||
<path
|
||||
android:fillColor="#FFF4FA"
|
||||
android:fillAlpha="0.68"
|
||||
android:pathData="M27,42C28,32 34,27 42,27C48,27 52,30 54,35L54,41C47,36 37,36 27,42Z" />
|
||||
<path
|
||||
android:fillColor="#F3E8FF"
|
||||
android:fillAlpha="0.52"
|
||||
android:pathData="M54,35C57,30 62,27 69,27C78,27 84,32 85,42C75,36 65,36 54,41Z" />
|
||||
<path
|
||||
android:fillColor="#6D2B55"
|
||||
android:fillAlpha="0.22"
|
||||
android:pathData="M22,48C29,62 45,75 54,85L54,89C45,79 27,65 22,48Z" />
|
||||
<path
|
||||
android:fillColor="#56306F"
|
||||
android:fillAlpha="0.22"
|
||||
android:pathData="M86,48C79,62 63,75 54,85L54,89C63,79 81,65 86,48Z" />
|
||||
</vector>
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/closer_launcher_foreground"
|
||||
android:gravity="fill" />
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M54,85C49,79 27,62 22,48C17,35 24,24 37,24C45,24 51,28 54,35C57,28 63,24 71,24C84,24 91,35 86,48C81,62 59,79 54,85Z" />
|
||||
</vector>
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/closer_launcher_monochrome"
|
||||
android:gravity="fill" />
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ This is the working artwork asset set for Closer. It keeps the existing purple/p
|
|||
and the 2D pastel couple illustration style, but gives every visual surface a clearer job.
|
||||
|
||||
The brand should feel like a private ritual for two people: warm, quiet, equal, and intentional.
|
||||
The heart remains the compact brand mark. The couple artwork should become the primary visual
|
||||
language anywhere there is enough space to show a human moment.
|
||||
The compact brand mark is now the approved Closer C-heart-keyhole. The couple artwork should remain
|
||||
the primary visual language anywhere there is enough space to show a human moment.
|
||||
|
||||
## Current Artwork Review
|
||||
|
||||
|
|
@ -33,19 +33,21 @@ Avoid:
|
|||
|
||||
Keep:
|
||||
|
||||
- `docs/store/sources/app-icon.svg` as the source of the compact heart mark.
|
||||
- `docs/brand/sources/closer-approved-icon-source.png` as the visual source of truth for the compact
|
||||
C-heart-keyhole mark.
|
||||
- `docs/store/sources/app-icon.svg` as the source wrapper for the launcher/store icon.
|
||||
- `iphone/Closer/Resources/illustration-couple-*.png` as the human brand style.
|
||||
- `iphone/Closer/Resources/pack-art-*.png` as the category/pack art direction.
|
||||
- `iphone/Closer/Resources/particle-heart.png` and `particle-petal.png` for celebration moments.
|
||||
|
||||
Improve:
|
||||
|
||||
- The heart is strong for launchers and tiny UI, but too generic when used alone at larger sizes.
|
||||
Use couple illustrations for onboarding, paywall, empty states, store screenshots, and web/social.
|
||||
- The mark is strong for launchers, auth, loaders, notification glyphs, and small privacy seals. Use
|
||||
couple illustrations for onboarding, paywall, empty states, store screenshots, and web/social.
|
||||
- Android currently has the launcher vectors but not the larger illustration library. Mirror the
|
||||
iOS resources into Android when those screens start using raster art.
|
||||
- Store feature graphics should show the heart plus one illustrated/private ritual scene, not only
|
||||
cards and symbols.
|
||||
- Store feature graphics should show the C-heart-keyhole plus one illustrated/private ritual scene,
|
||||
not only cards and symbols.
|
||||
|
||||
### Notification Artwork
|
||||
|
||||
|
|
@ -63,18 +65,20 @@ This section is only about the artwork used to represent notifications, not noti
|
|||
|
||||
| Asset | Use | Source / Target |
|
||||
| --- | --- | --- |
|
||||
| Primary app mark | Launcher, favicon, small brand moments | `docs/store/sources/app-icon.svg` |
|
||||
| Primary app mark | Launcher, favicon, small brand moments | `docs/brand/sources/closer-approved-icon-source.png` |
|
||||
| Adaptive foreground | Android launcher layer | `app/src/main/res/drawable/ic_launcher_foreground.xml` |
|
||||
| Adaptive background | Android launcher layer | `app/src/main/res/drawable/ic_launcher_background.xml` |
|
||||
| Monochrome mark | Android themed icon, single-color use | `app/src/main/res/drawable/ic_launcher_monochrome.xml` |
|
||||
| Notification glyph | Android/iOS notification art direction | Needed: monochrome heart/paired-card source |
|
||||
| Notification glyph | Android notification small icon | `app/src/main/res/drawable-nodpi/ic_notification_closer.png` |
|
||||
| Wordmark lockup | Website, store hero, press kit | Needed: `docs/brand/sources/closer-lockup.svg` |
|
||||
| Horizontal logo | Email header, social headers | Needed: SVG + PNG exports |
|
||||
| One-color logo | Legal docs, monochrome print, dark/light footer | Needed: SVG exports |
|
||||
| Favicon set | Website/browser/PWA | Needed: 16, 32, 48, 180, 192, 512 px |
|
||||
|
||||
Logo rule: the heart mark should mean "two equal people meeting in the middle." Do not split it,
|
||||
rotate it, add faces, add text inside it, or use it as a reaction emoji.
|
||||
Logo rule: the mark should mean "Closer is the private space around your relationship." Keep the
|
||||
approved pink upper C, lavender lower sweep, heart-shaped inner space, and true keyhole. Do not
|
||||
redraw it as a generic `C`, turn the keyhole into a heart, add a key or lock shackle, add faces, add
|
||||
text inside it, or use it as a reaction emoji.
|
||||
|
||||
### 2. App Icons
|
||||
|
||||
|
|
@ -86,9 +90,8 @@ rotate it, add faces, add text inside it, or use it as a reaction emoji.
|
|||
|
||||
Current status:
|
||||
|
||||
- Android and Play icon assets exist.
|
||||
- iOS has an asset catalog folder but no visible app icon set in the checked file list. Add the
|
||||
full iOS app icon set before TestFlight/App Store review.
|
||||
- Android, Play, and iOS AppIcon assets exist and should be regenerated from the approved source
|
||||
image after any future mark change.
|
||||
|
||||
### 3. Illustration Library
|
||||
|
||||
|
|
@ -158,7 +161,7 @@ These should be simple single-color vectors that work at 20-32 dp/pt.
|
|||
|
||||
| Asset | Spec | Direction |
|
||||
| --- | --- | --- |
|
||||
| Play feature graphic | 1024 x 500 PNG | Heart + one private ritual illustration + short promise |
|
||||
| Play feature graphic | 1024 x 500 PNG | C-heart-keyhole + one private ritual illustration + short promise |
|
||||
| Play screenshots | Up to 8 phone screenshots | Use product screens; omit login unless testing trust |
|
||||
| App Store screenshots | iPhone 6.7", 6.5", 5.5" as needed | Mirror Play story |
|
||||
| Social open graph | 1200 x 630 | Couple illustration + "A private space for two." |
|
||||
|
|
@ -234,10 +237,9 @@ iphone/Closer/Resources/
|
|||
|
||||
## Priority Build List
|
||||
|
||||
1. Keep the current heart mark, but create wordmark/horizontal/one-color SVG lockups.
|
||||
2. Add the missing iOS AppIcon set.
|
||||
3. Mirror illustration PNGs into Android and start using them in onboarding, empty states, paywall,
|
||||
1. Build wordmark/horizontal/one-color lockups around the approved C-heart-keyhole.
|
||||
2. Mirror illustration PNGs into Android and start using them in onboarding, empty states, paywall,
|
||||
and store screenshots.
|
||||
4. Rework the Play feature graphic to include one couple/private-ritual illustration.
|
||||
5. Add the new notification and privacy illustrations listed above.
|
||||
6. Build a small custom glyph set for privacy/reveal/date/capsule concepts.
|
||||
3. Rework the Play feature graphic to include one couple/private-ritual illustration.
|
||||
4. Add the new notification and privacy illustrations listed above.
|
||||
5. Build a small custom glyph set for privacy/reveal/date/capsule concepts.
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 908 KiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 271 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 271 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 426 KiB |
|
After Width: | Height: | Size: 425 KiB |
|
After Width: | Height: | Size: 425 KiB |
|
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1254" height="1254" viewBox="0 0 1254 1254" role="img" aria-label="Closer C-heart-keyhole mark">
|
||||
<image href="docs/brand/sources/closer-mark-transparent-keyhole-aubergine.png" width="1254" height="1254" preserveAspectRatio="xMidYMid meet"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 297 B |
|
|
@ -11,14 +11,21 @@ requirements, see `docs/brand/asset-system.md`.
|
|||
|
||||
## Brand mark
|
||||
|
||||
The mark is one heart formed by two equal halves. Pink and lavender represent two people meeting at
|
||||
the center; neither side visually dominates. Keep the mark intact and do not separate, rotate, add
|
||||
text inside, or place it directly over a busy image.
|
||||
The mark is the approved Closer `C-heart-keyhole`: a soft pink upper `C`, lavender lower sweep,
|
||||
a heart-shaped inner space, and a true centered keyhole. Pink and lavender represent two people
|
||||
meeting in one private space; the keyhole represents trust and privacy.
|
||||
|
||||
- Master mark source: `docs/brand/sources/closer-approved-icon-source.png`
|
||||
- Transparent mark source: `docs/brand/sources/closer-mark-transparent-keyhole-aubergine.png`
|
||||
- Launcher source: `docs/store/sources/app-icon.svg`
|
||||
- Android adaptive layers: `app/src/main/res/drawable/ic_launcher_*`
|
||||
- Android notification glyph: `app/src/main/res/drawable-nodpi/ic_notification_closer.png`
|
||||
- Minimum clear space: one quarter of the mark's width on all sides.
|
||||
- Minimum digital size: 24 px. At small sizes, use the solid monochrome mark.
|
||||
- Minimum digital size: 24 px. At small sizes, use the solid monochrome C-heart-keyhole glyph.
|
||||
|
||||
Keep the mark visually faithful to the approved artwork. Do not redraw it as a generic `C`, close
|
||||
the aperture into an `O`, add a padlock shackle, turn the keyhole into a heart, add a separate key,
|
||||
or place text inside the icon.
|
||||
|
||||
## Core colors
|
||||
|
||||
|
|
@ -64,8 +71,9 @@ when the couple key is unavailable. These claims describe deployed behavior.
|
|||
|
||||
- Store graphics and screenshots should use the same purple/pink palette as the product.
|
||||
- Lead with privacy and mutual connection before feature volume.
|
||||
- The Play feature graphic should show the heart mark, the primary promise, and compact product cues
|
||||
for private reveals, two-person use, and daily rituals. Do not turn it into a feature checklist.
|
||||
- The Play feature graphic should show the C-heart-keyhole mark, the primary promise, and compact
|
||||
product cues for private reveals, two-person use, and daily rituals. Do not turn it into a feature
|
||||
checklist.
|
||||
- Do not show intimate answer content, real email addresses, invite codes, or notification tokens.
|
||||
- Use clean demo data and crop out development indicators before publishing.
|
||||
- Re-export `docs/store/app-icon-512.png` and `docs/store/feature-graphic-1024x500.png` from the
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 477 KiB |
|
|
@ -1,17 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<filter id="shadow" x="-30%" y="-30%" width="160%" height="170%">
|
||||
<feDropShadow dx="0" dy="18" stdDeviation="14" flood-color="#24122F" flood-opacity=".42"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<rect width="512" height="512" fill="#4A235F"/>
|
||||
<path d="M0 0h512v198C411 236 314 232 222 207 124 180 52 125 0 64Z" fill="#B98AF4" opacity=".24"/>
|
||||
<path d="M0 354c92-52 192-45 296-15 93 27 157 37 216 1v172H0Z" fill="#24122F" opacity=".36"/>
|
||||
<path d="M378 0h134v512H352c6-74-5-148-34-219-34-82-27-171 60-293Z" fill="#B98AF4" opacity=".10"/>
|
||||
<g filter="url(#shadow)">
|
||||
<path d="M256 402c-25-28-130-111-153-177-24-62 13-116 73-116 38 0 65 19 80 49Z" fill="#F7C8E4"/>
|
||||
<path d="M256 402c25-28 130-111 153-177 24-62-13-116-73-116-38 0-65 19-80 49Z" fill="#D9B8FF"/>
|
||||
<path d="M122 199c7-45 36-69 78-69 27 0 46 11 56 28v28c-44-25-89-21-134 13Z" fill="#FFF4FA" opacity=".68"/>
|
||||
<path d="M256 158c14-18 38-28 67-28 42 0 70 24 77 69-49-34-97-38-144-13Z" fill="#F3E8FF" opacity=".52"/>
|
||||
</g>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512" role="img" aria-label="Closer app icon">
|
||||
<image href="docs/brand/sources/closer-approved-icon-square.png" width="512" height="512" preserveAspectRatio="xMidYMid slice"/>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 266 B |
|
|
@ -13,17 +13,7 @@
|
|||
<path d="M0 372c144-62 292-57 449-14 142 39 259 43 358 7 70-25 142-33 217-13v148H0Z" fill="#180C20" opacity=".34"/>
|
||||
<path d="M900 0h124v500H820c31-76 34-154 8-236C800 174 812 88 900 0Z" fill="#F7C8E4" opacity=".08"/>
|
||||
|
||||
<g transform="translate(68 74)">
|
||||
<rect x="0" y="0" width="312" height="312" rx="72" fill="#4A235F"/>
|
||||
<path d="M0 0h312v120C250 143 192 141 136 126 76 109 32 76 0 38Z" fill="#B98AF4" opacity=".24"/>
|
||||
<path d="M0 216c56-32 117-28 181-9 57 16 96 23 131 1v104H0Z" fill="#180C20" opacity=".28"/>
|
||||
<g transform="translate(0 -4)" filter="url(#markShadow)">
|
||||
<path d="M156 246c-15-17-79-68-93-109-15-38 8-71 45-71 23 0 40 12 48 30Z" fill="#F7C8E4"/>
|
||||
<path d="M156 246c15-17 79-68 93-109 15-38-8-71-45-71-23 0-40 12-48 30Z" fill="#D9B8FF"/>
|
||||
<path d="M74 121c4-28 22-42 48-42 16 0 28 7 34 17v17c-27-15-54-13-82 8Z" fill="#FFF4FA" opacity=".68"/>
|
||||
<path d="M156 96c9-11 23-17 41-17 26 0 43 14 47 42-30-21-59-23-88-8Z" fill="#F3E8FF" opacity=".52"/>
|
||||
</g>
|
||||
</g>
|
||||
<image x="68" y="74" width="312" height="312" href="docs/brand/sources/closer-approved-icon-square.png" preserveAspectRatio="xMidYMid slice" filter="url(#markShadow)"/>
|
||||
|
||||
<text x="408" y="142" fill="#FFF8FC" font-family="DejaVu Sans" font-size="72" font-weight="700">Closer</text>
|
||||
<text x="412" y="198" fill="#F7C8E4" font-family="DejaVu Sans" font-size="26" font-weight="600">A private space</text>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.3 KiB |
|
|
@ -7,7 +7,7 @@ struct LoadingView: View {
|
|||
|
||||
var body: some View {
|
||||
VStack(spacing: CloserSpacing.lg) {
|
||||
CloserHeartLoader()
|
||||
CloserMarkLoader()
|
||||
Text(message)
|
||||
.font(CloserFont.callout)
|
||||
.foregroundColor(.closerTextSecondary)
|
||||
|
|
@ -17,167 +17,44 @@ struct LoadingView: View {
|
|||
}
|
||||
|
||||
struct CloserHeartLoader: View {
|
||||
var size: CGFloat = 76
|
||||
|
||||
var body: some View {
|
||||
CloserMarkLoader(size: size)
|
||||
}
|
||||
}
|
||||
|
||||
struct CloserMarkLoader: View {
|
||||
@Environment(\.accessibilityReduceMotion) private var reduceMotion
|
||||
@State private var fillProgress: CGFloat = 0.08
|
||||
@State private var isPulsing = false
|
||||
|
||||
var size: CGFloat = 76
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
CloserHeartShape()
|
||||
.fill(
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(hex: "F7C8E4").opacity(0.22),
|
||||
Color(hex: "D9B8FF").opacity(0.22)
|
||||
],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
)
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Color(hex: "F7C8E4")
|
||||
Color(hex: "D9B8FF")
|
||||
}
|
||||
.mask(CloserHeartShape())
|
||||
.mask(alignment: .bottom) {
|
||||
Rectangle()
|
||||
.frame(height: size * (reduceMotion ? 1 : fillProgress))
|
||||
}
|
||||
|
||||
CloserHeartHighlightShape(side: .left)
|
||||
.fill(Color(hex: "FFF4FA").opacity(reduceMotion ? 0.68 : 0.68 * fillProgress))
|
||||
|
||||
CloserHeartHighlightShape(side: .right)
|
||||
.fill(Color(hex: "F3E8FF").opacity(reduceMotion ? 0.52 : 0.52 * fillProgress))
|
||||
}
|
||||
Image("closer-mark-loader")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: size, height: size)
|
||||
.scaleEffect(reduceMotion ? 1 : (isPulsing ? 1.04 : 0.96))
|
||||
.accessibilityHidden(true)
|
||||
.onAppear {
|
||||
guard !reduceMotion else {
|
||||
fillProgress = 1
|
||||
isPulsing = false
|
||||
return
|
||||
}
|
||||
|
||||
withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: false)) {
|
||||
fillProgress = 1
|
||||
}
|
||||
withAnimation(.easeInOut(duration: 0.9).repeatForever(autoreverses: true)) {
|
||||
isPulsing = true
|
||||
}
|
||||
}
|
||||
.onChange(of: reduceMotion) { _, newValue in
|
||||
if newValue {
|
||||
fillProgress = 1
|
||||
isPulsing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct CloserHeartShape: Shape {
|
||||
func path(in rect: CGRect) -> Path {
|
||||
var path = Path()
|
||||
path.move(to: point(54, 85, in: rect))
|
||||
path.addCurve(
|
||||
to: point(22, 48, in: rect),
|
||||
control1: point(49, 79, in: rect),
|
||||
control2: point(27, 62, in: rect)
|
||||
)
|
||||
path.addCurve(
|
||||
to: point(37, 24, in: rect),
|
||||
control1: point(17, 35, in: rect),
|
||||
control2: point(24, 24, in: rect)
|
||||
)
|
||||
path.addCurve(
|
||||
to: point(54, 35, in: rect),
|
||||
control1: point(45, 24, in: rect),
|
||||
control2: point(51, 28, in: rect)
|
||||
)
|
||||
path.addCurve(
|
||||
to: point(71, 24, in: rect),
|
||||
control1: point(57, 28, in: rect),
|
||||
control2: point(63, 24, in: rect)
|
||||
)
|
||||
path.addCurve(
|
||||
to: point(86, 48, in: rect),
|
||||
control1: point(84, 24, in: rect),
|
||||
control2: point(91, 35, in: rect)
|
||||
)
|
||||
path.addCurve(
|
||||
to: point(54, 85, in: rect),
|
||||
control1: point(81, 62, in: rect),
|
||||
control2: point(59, 79, in: rect)
|
||||
)
|
||||
path.closeSubpath()
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
private struct CloserHeartHighlightShape: Shape {
|
||||
enum Side {
|
||||
case left
|
||||
case right
|
||||
}
|
||||
|
||||
let side: Side
|
||||
|
||||
func path(in rect: CGRect) -> Path {
|
||||
var path = Path()
|
||||
switch side {
|
||||
case .left:
|
||||
path.move(to: point(27, 42, in: rect))
|
||||
path.addCurve(
|
||||
to: point(42, 27, in: rect),
|
||||
control1: point(28, 32, in: rect),
|
||||
control2: point(34, 27, in: rect)
|
||||
)
|
||||
path.addCurve(
|
||||
to: point(54, 35, in: rect),
|
||||
control1: point(48, 27, in: rect),
|
||||
control2: point(52, 30, in: rect)
|
||||
)
|
||||
path.addLine(to: point(54, 41, in: rect))
|
||||
path.addCurve(
|
||||
to: point(27, 42, in: rect),
|
||||
control1: point(47, 36, in: rect),
|
||||
control2: point(37, 36, in: rect)
|
||||
)
|
||||
path.closeSubpath()
|
||||
case .right:
|
||||
path.move(to: point(54, 35, in: rect))
|
||||
path.addCurve(
|
||||
to: point(69, 27, in: rect),
|
||||
control1: point(57, 30, in: rect),
|
||||
control2: point(62, 27, in: rect)
|
||||
)
|
||||
path.addCurve(
|
||||
to: point(85, 42, in: rect),
|
||||
control1: point(78, 27, in: rect),
|
||||
control2: point(84, 32, in: rect)
|
||||
)
|
||||
path.addCurve(
|
||||
to: point(54, 41, in: rect),
|
||||
control1: point(75, 36, in: rect),
|
||||
control2: point(65, 36, in: rect)
|
||||
)
|
||||
path.closeSubpath()
|
||||
}
|
||||
return path
|
||||
}
|
||||
}
|
||||
|
||||
private func point(_ x: CGFloat, _ y: CGFloat, in rect: CGRect) -> CGPoint {
|
||||
CGPoint(
|
||||
x: rect.minX + rect.width * (x / 108),
|
||||
y: rect.minY + rect.height * (y / 108)
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - Error View
|
||||
|
||||
struct ErrorView: View {
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 908 KiB |
|
Before Width: | Height: | Size: 833 B After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 23 KiB |