adding the blogs

This commit is contained in:
itsamejms
2026-04-30 21:42:02 +01:00
parent bc920a292d
commit 931063b7c3
28 changed files with 1762 additions and 0 deletions
+42
View File
@@ -14,6 +14,7 @@
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"lucide-react": "^0.577.0",
"marked": "^18.0.2",
"motion": "^12.23.24",
"react": "^19.2.4",
"react-dom": "^19.2.4",
@@ -22,6 +23,8 @@
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"autoprefixer": "^10.4.27",
"gh-pages": "^6.3.0",
"tailwindcss": "^4.2.2",
@@ -1429,6 +1432,26 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.2.0"
}
},
"node_modules/@vitejs/plugin-react": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
@@ -1640,6 +1663,13 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"license": "MIT"
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -2358,6 +2388,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/marked": {
"version": "18.0.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-18.0.2.tgz",
"integrity": "sha512-NsmlUYBS/Zg57rgDWMYdnre6OTj4e+qq/JS2ot3KrYLSoHLw+sDu0Nm1ZGpRgYAq6c+b1ekaY5NzVchMCQnzcg==",
"license": "MIT",
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+3
View File
@@ -20,6 +20,7 @@
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"lucide-react": "^0.577.0",
"marked": "^18.0.2",
"motion": "^12.23.24",
"react": "^19.2.4",
"react-dom": "^19.2.4",
@@ -28,6 +29,8 @@
"tailwind-merge": "^3.5.0"
},
"devDependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"autoprefixer": "^10.4.27",
"gh-pages": "^6.3.0",
"tailwindcss": "^4.2.2",
+4
View File
@@ -3,6 +3,8 @@ import Index from "./pages/Index";
import Privacy from "./pages/Privacy";
import NotFound from "./pages/NotFound";
import Waitlist from "./pages/Waitlist";
import BlogList from "./pages/BlogList";
import BlogPost from "./pages/BlogPost";
import CookieConsent from "./components/ui/CookieConsent";
const App = () => (
@@ -12,6 +14,8 @@ const App = () => (
<Route path="/" element={<Index />} />
<Route path="/privacy" element={<Privacy />} />
<Route path="/waitlist" element={<Waitlist />} />
<Route path="/blog" element={<BlogList />} />
<Route path="/blog/:slug" element={<BlogPost />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} />
</Routes>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

@@ -0,0 +1,136 @@
---
title: "Best Off-Leash Areas for Dogs in Lagos (and Nearby)"
date: 2025-05-01
description: "Looking for off-leash areas for dogs in Lagos, Portugal? Discover the best spots for safe, enjoyable off-leash walks, plus what to watch out for."
category: Insights
readTime: "6 min"
live: true
---
### **Introduction**
Finding a good place to let your dog off-leash in Lagos isnt always straightforward.
Some areas are:
* Too busy
* Too unpredictable
* Or not actually suitable for your dog
But when you find the right spot, it changes everything.
Here are some of the best off-leash-friendly areas around Lagos, and how to choose the right one for your dog.
---
## **1\. Mata Nacional de Barão de São João**
**Best for:** Most dogs
**Why it works:**
* Wide, open forest trails
* Plenty of space to create distance
* Shaded and cooler
Ideal for dogs who need **space without isolation**
---
## **2\. Praia da Bordeira (West Coast)**
**Best for:** High-energy dogs
**Why it works:**
* Huge open beach
* Low crowd density (outside peak times)
* Great for running
Watch:
* Wind exposure
* Seasonal rules
---
## **3\. Ria de Alvor (Quiet Sections)**
**Best for:** Lower-energy or calm dogs
**Why it works:**
* Flat terrain
* Open visibility
Avoid peak hours, it can get busy
---
## **4\. Monchique Countryside**
**Best for:** Dogs needing low stimulation
**Why it works:**
* Quiet
* Natural
* Cooler temperatures
Great for reactive or sensitive dogs
---
## **5\. Local Countryside Trails (Hidden Gems)**
Often overlooked, but some of the best options.
These areas offer:
* Lower dog traffic
* More control
* Flexible space
Especially useful for:
* Training recall
* Building confidence
Check out trails near countryside villages like Bensafrim and Vila do Bispo.
---
## **Important: Not Every “Off-Leash Area” Is Right**
Just because a place allows off-leash doesnt mean its suitable.
Before letting your dog off:
* Check the environment
* Look at dog traffic
* Consider your dogs behaviour
---
## **What Makes a Good Off-Leash Spot?**
The best places have:
* Space to move freely
* Clear visibility
* Low unpredictability
* An environment suited to your dog
---
## **Final Thoughts**
The goal isnt just to let your dog off-leash.
Its to do it in a way that:
* Feels safe
* Feels calm
* And actually benefits your dog
Because the right environment makes all the difference.
<!-- **Want help finding the right trails for your dog? Download TailTrails here.** -->
@@ -0,0 +1,141 @@
---
title: "How to Choose the Right Walk for Your Dog (Based on Their Personality and Needs)"
date: 2025-04-20
description: "Not all walks are equal. Learn how to choose the right environment for your dog based on energy, temperament, and needs."
category: Strategy
readTime: "5 min"
live: true
---
### **Introduction**
Choosing a walk for your dog often comes down to convenience.
Whats nearby.
What youve done before.
What seems “good enough.”
But the best walks arent chosen that way.
Theyre chosen based on **your dog**.
---
### **Step 1: Understand Your Dogs Profile**
Start with the basics:
* Energy level (low, medium, high)
* Sociability (loves dogs vs prefers space)
* Confidence (bold vs cautious)
* Sensitivities (noise, movement, crowds)
* Physical needs (age, injuries, endurance)
This becomes your baseline.
---
### **Step 2: Match the Environment**
Now consider the walk itself:
#### **High-energy dogs**
Need:
* Long routes
* Open space
* Opportunities to run
Best environments:
* Wide trails
* Open countryside
* Low restriction areas
---
**Sensitive or reactive dogs**
Need:
* Predictability
* Space from others
* Low stimulation
Best environments:
* Quiet trails
* Low dog traffic areas
* Wide paths with visibility
---
#### **Older or recovering dogs**
Need:
* Shorter distances
* Even terrain
* Shade
Best environments:
* Flat trails
* Forest paths
* Low-exertion routes
---
### **Step 3: Look Beyond the Surface**
A trail might look perfect, but details matter:
* Is it narrow or open?
* Is there shade?
* Are dogs typically off-leash there?
* Is it busy at certain times?
These small factors make a big difference.
---
### **Step 4: Pay Attention to Feedback**
Your dog will tell you if its working.
Look for:
* Relaxed body language
* Natural curiosity
* Steady energy (not frantic or shut down)
* Willing engagement with surroundings
If youre seeing stress signals, it may not be the right fit.
---
### **Step 5: Prioritise Variety Intentionally**
Variety is important.
But random variety isnt helpful.
Instead:
* Rotate between environments that suit your dog
* Gradually introduce new challenges
* Build confidence over time
---
### **Final Thoughts**
The best walk isnt the longest or the most popular.
Its the one that fits your dog.
Because when you choose the right environment, youre not just going for a walk, youre creating a better experience for both of you.
<!-- **Want help finding the right trails for your dog? Download TailTrails here.** -->
@@ -0,0 +1,130 @@
---
title: "How to Give a Reactive Dog a Fulfilling Life (Without Avoiding the World)"
date: 2025-03-15
description: "Have a reactive dog? Learn how to give them a fulfilling life with the right environments, structure, and approach."
category: Insights
readTime: "7 min"
live: true
---
### **Introduction**
Living with a reactive dog can feel limiting.
You might:
* Avoid certain places
* Walk at odd hours
* Stick to the same safe routes
But heres the truth:
Your dog can still have a full, enriching life.
It just requires a different approach.
---
## **What Reactivity Often Means**
Reactivity isnt “bad behaviour.”
Its usually:
* Overstimulation
* Fear or uncertainty
* Lack of space
* Too much, too quickly
---
## **Environment Matters More Than Training Alone**
Training is important, but environment is foundational.
Even a well-trained dog can struggle in:
* Busy areas
* Narrow paths
* High dog-traffic zones
---
## **Choosing the Right Environments**
Look for places that offer:
* Space to move away
* Predictability
* Lower levels of stimulation
Examples:
* Wide trails
* Quiet countryside
* Off-peak walking times
---
## **Give Them the Right Kind of Freedom**
Reactive dogs dont need:
* More pressure
* More exposure too quickly
They need:
* Controlled experiences
* Space to decompress
* Opportunities to succeed
---
## **Common Mistakes**
* Taking them to busy “dog-friendly” areas
* Forcing social interactions
* Ignoring early stress signals
---
## **Building Confidence Over Time**
Start small.
* Familiar environments
* Gradual changes
* Positive reinforcement
Confidence grows through:
Consistent, successful experiences
---
## **Fulfilment Looks Different**
A fulfilling life doesnt mean:
* Busy parks
* Social environments
It means:
* Calm exploration
* Comfortable movement
* Feeling safe in the world
---
## **Final Thoughts**
Your dog doesnt need to handle everything.
They just need the **right environments to thrive in**.
And when you give them that, everything starts to shift.
<!-- **Want help finding the right trails for your dog? Download TailTrails here.** -->
@@ -0,0 +1,134 @@
---
title: "Off-Leash Dog Rules in Portugal: What You Need to Know in 2026"
date: 2026-04-01
description: "Can you walk your dog off-leash in Portugal? Learn the rules, risks, and best practices for safe and legal off-leash walks."
category: Trends
readTime: "6 min"
live: true
---
### **Introduction**
Letting your dog off-leash is one of the simplest ways to give them freedom, enrichment, and a more natural experience.
But if youre living in or visiting Portugal, the rules arent always obvious, and getting it wrong can lead to fines, unsafe situations, or conflict with others.
Heres what you need to know.
---
### **Are Dogs Allowed Off-Leash in Portugal?**
In general, dogs in Portugal are expected to be **under control at all times in public spaces**.
That doesnt always mean physically on a leash \- but it *does* mean:
* Your dog must respond reliably to commands
* They must not pose a risk to people, other animals, or property
* They must not cause disturbance
In many **urban areas**, leash use is effectively required.
In more **rural or remote areas**, off-leash walking is more commonly accepted \- but still comes with responsibility.
---
### **Official Rules, Restrictions, and Fines**
In Portugal, there are also specific legal restrictions to be aware of, especially around beaches and public spaces. During the summer season (typically June to September), many beaches prohibit dogs entirely, regardless of whether they are on or off-leash. These rules are set by local municipalities and are usually clearly signposted at beach entrances. Outside of summer, some beaches become dog-friendly again, but leash expectations can still apply.
Failing to follow these rules can result in on-the-spot fines, which typically range from around €25 up to €250 or more, depending on the situation and location. Additional penalties may apply if a dog is considered out of control, causes disturbance, or poses a risk to others.
Because enforcement and rules can vary by region, its always worth checking local signage before letting your dog off-leash, especially in popular or seasonal areas.
---
### **Where Off-Leash Walking Is (Usually) Acceptable**
Youre more likely to safely and responsibly walk your dog off-leash in:
* Quiet countryside trails
* Forest paths with low foot traffic
* Remote coastal routes (away from crowds)
* Open land where visibility is high
However, even in these areas, its not just about permission \- its about suitability for your dog.
---
### **Where You Should Keep Your Dog On-Leash**
There are several environments where off-leash walking is **not recommended or restricted**:
* Busy beaches (especially during peak season)
* Urban parks and promenades
* Narrow cliff paths
* Areas with livestock or wildlife
* High dog-traffic zones
Even if technically allowed, these environments can be:
* Overstimulating
* Unsafe
* Stressful for reactive or sensitive dogs
---
### **The Risks Most Dog Owners Overlook**
Many owners assume off-leash \= freedom.
But without the right environment, it can quickly become the opposite.
Common risks include:
* Uncontrolled interactions with other dogs
* Encounters with wildlife or livestock
* Sudden terrain hazards (cliffs, unstable ground)
* Overheating in exposed areas
* Poor recall in unfamiliar settings
The result? Stress for both you *and* your dog.
---
### **Why “Dog-Friendly” Isnt Enough**
A beach might allow dogs.
A trail might be popular with dog walkers.
But that doesnt mean its **right for your dog**.
Every dog has different needs:
* Some thrive in social, high-energy spaces
* Others need quiet, predictable environments
* Some need open space to run
* Others need structured, low-stimulation walks
Choosing the wrong environment can undo the very thing youre trying to give them.
---
### **How to Choose the Right Off-Leash Environment**
Before letting your dog off-leash, ask:
* How busy is this area?
* Are there other dogs nearby?
* Is the terrain safe and visible?
* Is there wildlife or livestock?
* Will my dog feel confident here, or overwhelmed?
The goal isnt just freedom.
Its **setting your dog up for a good time**.
---
### **Final Thoughts**
Walking your dog off-leash in Portugal can be an awesome experience, when its done in the right place, at the right time, for the right dog.
Because its not just about whether dogs are allowed.
Its about whether the environment actually fits.
<!-- **Want help finding the right trails for your dog? Download TailTrails here.** -->
@@ -0,0 +1,125 @@
---
title: "The Importance of Variety in Your Dogs Life (And How to Do It Right)"
date: 2025-02-28
description: "Dogs need more than the same daily walk. Learn why variety matters and how to safely introduce new environments into your dogs routine."
category: Strategy
readTime: "5 min"
live: true
---
### **Introduction**
Most dogs live very predictable lives.
The same route.
The same pace.
The same environment.
And while routine has its place, too much of it can limit your dogs experience of the world.
---
## **Why Variety Matters**
Dogs are naturally curious.
Theyre wired to:
* Explore
* Smell
* Navigate new environments
When every walk is the same, they miss out on:
* Mental stimulation
* Physical engagement
* Confidence-building experiences
---
## **Its Not Just About Exercise**
A longer walk doesnt always mean a better walk.
A short walk in a new environment can be:
* More stimulating
* More engaging
* More fulfilling
---
## **The Benefits of Variety**
Introducing new environments can:
* Reduce boredom
* Improve behaviour
* Build confidence
* Strengthen your bond
---
## **But Random Variety Isnt the Answer**
This is where many owners get it wrong.
Throwing your dog into:
* Busy environments
* Unpredictable situations
* Overstimulating spaces
Can actually:
* Increase stress
* Lead to reactivity
* Create negative experiences
---
## **How to Introduce Variety Properly**
### **1\. Start with similar environments**
If your dog is used to quiet trails, try new *quiet* trails first.
---
### **2\. Change one variable at a time**
* New location, same energy level
* New terrain, same level of stimulation
---
### **3\. Watch your dogs response**
Let their behaviour guide your decisions.
---
### **4\. Build gradually**
Confidence grows with positive experiences.
---
## **The Goal**
Not constant novelty.
But **intentional variation** that suits your dog.
---
## **Final Thoughts**
Your dog doesnt need more walks.
They need **better, more varied experiences**.
Because when you get it right, even a simple walk can feel completely different.
<!-- **Want help finding the right trails for your dog? Download TailTrails here.** -->
@@ -0,0 +1,159 @@
---
title: "Top 10 Hikes to do with your Dog in Lagos and the Algarve"
date: 2025-06-10
description: "Discover the best hikes in Lagos and the Algarve to take your dog on. From coastal trails to quiet forests, find the right walk for your dog."
category: Insights
readTime: "8 min"
live: true
---
### **Introduction**
If youre based in or visiting Lagos, youre surrounded by some of the most beautiful walking routes in Europe.
But not all hikes are equal \- especially when youre walking with a dog.
Some are:
* Too exposed
* Too busy
* Too steep
* Or just not suited to your dogs needs
Here are 10 of the best dog-friendly hikes in the Algarve, plus what makes each one a good fit.
---
## **1\. Ponta da Piedade Coastal Walk**
**Best for:** Calm, confident dogs
**Type:** Cliffside / scenic
Incredible views, but:
* Narrow in parts
* High foot traffic
Best early morning or off-season
---
## **2\. Mata Nacional de Barão de São João**
**Best for:** Most dogs
**Type:** Forest / shaded
* Wide trails
* Plenty of shade
* Lower stimulation
One of the most versatile options near Lagos
---
## **3\. Ria de Alvor Nature Reserve**
**Best for:** Low to moderate energy dogs
**Type:** Boardwalk / coastal
* Flat and accessible
* Can be busy
* Limited shade
---
## **4\. Rocha da Pena (near Loulé)**
**Best for:** High-energy, confident dogs
**Type:** Rocky / inland
* More challenging terrain
* Great for adventurous dogs
---
## **5\. Praia da Bordeira (West Coast)**
**Best for:** High-energy dogs
**Type:** Beach / open space
* Huge open areas
* Ideal for running
* Watch for wind \+ exposure
---
## **6\. Monchique Forest Trails**
**Best for:** Hot days / sensitive dogs
**Type:** Mountain / forest
* Cooler temperatures
* Shaded routes
* Varied terrain
---
## **7\. Lagos to Praia da Luz Walk**
**Best for:** Moderate energy dogs
**Type:** Coastal mix
* Combination of paths and views
* Can be busy in sections
---
## **8\. Parque Natural do Sudoeste Alentejano**
**Best for:** Dogs needing space
**Type:** Wild / remote
* Less crowded
* More natural terrain
---
## **9\. Seven Hanging Valleys Trail**
**Best for:** Experienced dogs only
**Type:** Cliffside
* Stunning but narrow
* Not suitable for reactive or unpredictable dogs
---
## **How to Choose the Right One**
Instead of asking:
“Which hike is best?”
Ask:
“Which hike is right for my dog?”
Consider:
* Energy level
* Sociability
* Confidence
* Weather
* Time of day
---
## **Final Thoughts**
The Algarve is full of incredible places to explore with your dog.
But the best walks arent just beautiful.
Theyre the ones that:
* Suit your dog
* Feel comfortable
* And allow them to be at their best
<!-- **Want help finding the right trails for your dog? Download TailTrails here.** -->
@@ -0,0 +1,201 @@
---
title: "Walking Your Dog in the Algarve: Hidden Dangers Every Owner Should Know"
date: 2025-07-05
description: "Walking your dog in the Algarve? Learn about hidden dangers like pine processionary caterpillars, grass seeds, heat, and more — plus how to keep your dog safe."
category: Tutorial
readTime: "7 min"
live: true
---
### **Introduction**
The Algarve is one of the best places in Europe to explore with your dog.
Cliffside trails.
Quiet forests.
Open countryside.
But beneath all that beauty, there are a few **less obvious risks** that many dog owners (especially newcomers) dont see coming.
Knowing what to look out for can make the difference between a great walk and an emergency vet visit.
---
## **1\. Pine Processionary Caterpillars (High Risk)**
These are one of the **most dangerous hazards** for dogs in Portugal.
They appear in:
* Pine forests
* Woodland trails
* Warmer months (typically late winter to spring)
### **Why theyre dangerous:**
Their tiny hairs contain a toxin that can cause:
* Severe swelling of the mouth and tongue
* Vomiting
* Difficulty breathing
* Tissue damage (in serious cases)
### **What to look for:**
* Caterpillars moving in a line (procession)
* Nests in pine trees (silk-like bundles)
### **What to do:**
* Avoid pine-heavy areas during peak season
* Keep your dog close in these environments
* If contact happens: **go to a vet immediately**
---
## **2\. Grass Seeds (Foxtails)**
Small, sharp, and incredibly problematic.
Theyre common in:
* Dry grasslands
* Trail edges
* Late spring and summer
### **Why theyre dangerous:**
They can lodge in:
* Noses
* Ears
* Eyes
* Between toes
And they dont come out easily—they move deeper into the body.
### **Signs to watch for:**
* Sneezing repeatedly
* Licking paws
* Head shaking
* Sudden discomfort
### **Prevention:**
* Avoid overgrown paths
* Check your dog after walks
* Pay close attention to paws and ears
---
## **3\. Heat and Sun Exposure**
The Algarve sun is intense, especially in summer.
Even when it doesnt feel extreme to you, it can be too much for your dog.
### **Risks:**
* Overheating
* Dehydration
* Burnt paw pads
### **Safer approach:**
* Walk early morning or late evening
* Choose shaded routes (forests \> exposed cliffs)
* Always carry water
* Test the ground temperature before walking
---
## **4\. Livestock and Wildlife**
Many rural trails pass through areas with:
* Wild cats
* Dog packs
* Goats
* Sheep
* Cattle
* Wildlife (including birds and small animals)
### **Why it matters:**
* Dogs with high prey drive may chase
* Farmers are protective of their animals
* Situations can escalate quickly
### **Best practice:**
* Keep your dog on-leash around animals
* Be aware of your surroundings
* Respect local land use
---
## **5\. Cliffs and Unstable Terrain**
The Algarve coastline is stunning, but not always forgiving.
### **Risks:**
* Loose rocks
* Narrow paths
* Sudden drops
### **Important:**
Even confident dogs can misjudge terrain.
### **Safer approach:**
* Avoid narrow cliff paths with reactive or excitable dogs
* Keep distance from edges
* Choose wider, more stable trails when possible
---
## **6\. High Dog Traffic Areas**
Some popular walking spots can be:
* Busy
* Unpredictable
* Overstimulating
This can be especially challenging for:
* Reactive dogs
* Nervous dogs
* Dogs in training
---
## **Why Environment Matters More Than You Think**
Most of these risks arent just about location.
Theyre about **fit**.
The same trail can be:
* Perfect for one dog
* Completely wrong for another
Thats why choosing where you walk isnt just about what looks good—its about what actually works for your dog.
---
## **Final Thoughts**
Walking your dog in the Algarve can be one of the best parts of living or visiting here.
But the best walks happen when you:
* Know what to look out for
* Choose environments intentionally
* Match the walk to your dogs needs
<!-- **Want help finding the right trails for your dog? Download TailTrails here.** -->
@@ -0,0 +1,147 @@
---
title: "Why “Dog-Friendly” Isnt Enough (And What Actually Matters)"
date: 2025-01-18
description: "“Dog-friendly” doesnt always mean suitable. Learn what really makes a walk right for your dog, and how to choose better environments."
category: Insights
readTime: "5 min"
live: true
---
### **Introduction**
“Dog-friendly” is everywhere.
Dog-friendly cafés.
Dog-friendly beaches.
Dog-friendly trails.
But heres the problem:
**Dog-friendly doesnt mean right for your dog.**
And relying on that label alone is one of the mistakes dog owners make.
---
### **The Problem with “Dog-Friendly”**
The term sounds helpful, but its incredibly broad.
A place can be dog-friendly and still be:
* Overcrowded
* Loud and overstimulating
* Too narrow or restrictive
* Unsafe for certain temperaments
* Physically unsuitable for your dog
It tells you one thing:
Dogs are allowed.
It tells you nothing about:
Whether your dog will actually enjoy being there.
---
### **Every Dog Experiences the World Differently**
This is where most advice falls short.
Dogs arent interchangeable.
Some are:
* Highly social and confident
* Sensitive to noise and movement
* Reactive around other dogs
* Driven by scent or prey
* Recovering from injury
* Low energy or ageing
The same environment can feel:
* Exciting for one dog
* Overwhelming for another
---
### **What Actually Makes a Walk “Good”**
A good walk isnt about distance or popularity.
Its about **fit**.
The right walk considers:
* **Dog traffic** busy vs quiet
* **Space** open vs narrow
* **Terrain** soft ground vs hard surfaces
* **Exposure** shaded vs direct sun
* **Environment** forest, beach, rural, urban
* **Stimulation level** predictable vs chaotic
When these align with your dogs needs, everything changes.
---
### **Signs Youre Choosing the Wrong Environments**
You might notice:
* Your dog becomes overstimulated or reactive
* They seem stressed or hyper-alert
* They pull, bark, or shut down
* Walks feel unpredictable or tense
Its not always a training issue.
Often, its an **environment mismatch**.
---
### **Why This Matters More Than Ever**
Modern dog ownership has shifted.
Dogs are:
* Living in more urban environments
* Exposed to more stimulation
* Expected to behave in unnatural settings
That means we need to be more intentional about:
Where we take them \- not just how often
---
### **A Better Way to Think About Walks**
Instead of asking:
“Is this place dog-friendly?”
Ask:
“Is this place right for my dog?”
That shift alone can:
* Reduce stress
* Improve behaviour
* Increase confidence
* Create better shared experiences
---
### **Final Thoughts**
Your dog doesnt need more places.
They need the **right places**.
Because when the environment fits, everything else gets easier.
<!-- **Want help finding the right trails for your dog? Download TailTrails here.** -->
+1
View File
@@ -26,6 +26,7 @@ const Header = () => {
<div className="hidden md:flex items-center gap-8">
<a href="#features" className="text-sm font-medium text-text-muted hover:text-brand transition-colors">Features</a>
<a href="#how-it-works" className="text-sm font-medium text-text-muted hover:text-brand transition-colors">How It Works</a>
<a href="/blog" className="text-sm font-medium text-text-muted" onClick={() => setIsMenuOpen(false)}>Blogs</a>
<a href="/waitlist" className="bg-brand hover:bg-brand-light text-bg-primary px-6 py-2 rounded-full text-sm font-bold transition-all transform hover:scale-105 active:scale-95 inline-flex items-center justify-center">
Join Waitlist
</a>
+28
View File
@@ -37,3 +37,31 @@
@utility bento-card {
@apply bg-bg-secondary border border-border-subtle rounded-3xl p-8 transition-all duration-300;
}
/* Semantic utility aliases to map theme variables to class names used across the app */
:root {
/* expose aliases for legacy class names */
}
.bg-background { background-color: var(--color-bg-primary); }
.bg-bg-primary { background-color: var(--color-bg-primary); }
.bg-bg-secondary { background-color: var(--color-bg-secondary); }
.bg-bg-variant { background-color: var(--color-bg-variant); }
.text-foreground { color: var(--color-text-primary); }
.text-text-primary { color: var(--color-text-primary); }
.text-text-secondary { color: var(--color-text-secondary); }
.text-text-muted { color: var(--color-text-muted); }
.text-muted-foreground { color: var(--color-text-muted); }
.border-border { border-color: var(--color-border-subtle); }
.border-border-subtle { border-color: var(--color-border-subtle); }
.bg-brand { background-color: var(--color-brand); }
.bg-brand-light { background-color: var(--color-brand-light); }
.text-bg-primary { color: var(--color-bg-primary); }
.bg-secondary { background-color: rgba(255,255,255,0.04); }
.text-secondary-foreground { color: var(--color-text-secondary); }
.glass { background-color: rgba(255,255,255,0.05); }
+73
View File
@@ -0,0 +1,73 @@
export type BlogPost = {
slug: string;
title: string;
date?: string; // created at / published date
description?: string;
content: string;
category?: string;
readTime?: string;
live?: boolean;
};
function slugify(name: string) {
return name
.toLowerCase()
.replace(/\.md$/, '')
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '');
}
function parseFrontmatter(raw: string) {
const fmMatch = raw.match(/^---\n([\s\S]*?)\n---/);
const fm: Record<string, any> = {};
let content = raw;
if (fmMatch) {
const body = fmMatch[1];
body.split(/\n+/).forEach((line) => {
const idx = line.indexOf(':');
if (idx > -1) {
const key = line.slice(0, idx).trim();
let val = line.slice(idx + 1).trim().replace(/^"|"$/g, '');
// coerce booleans
if (/^(true|false)$/i.test(val)) {
fm[key] = val.toLowerCase() === 'true';
} else {
fm[key] = val;
}
}
});
content = raw.slice(fmMatch[0].length).trim();
}
return { fm, content };
}
// Import all markdown files from the assets raw-blogs folder as raw strings
const modules = import.meta.glob('../assets/raw-blogs/*.md', { eager: true, as: 'raw' }) as Record<string, string>;
const posts = Object.entries(modules).map(([path, raw]) => {
const file = path.split('/').pop()!;
const slug = slugify(file);
const { fm, content } = parseFrontmatter(raw);
return {
slug,
title: fm.title ?? file.replace(/\.md$/, ''),
date: fm.date,
description: fm.description,
content,
category: fm.category,
readTime: fm.readTime,
live: typeof fm.live === 'boolean' ? fm.live : fm.live === 'true',
} as BlogPost;
}).sort((a, b) => {
const da = a.date ?? '';
const db = b.date ?? '';
return db.localeCompare(da);
});
export function getAllPosts(): BlogPost[] {
return posts;
}
export function getPostBySlug(slug: string): BlogPost | undefined {
return posts.find((p) => p.slug === slug);
}
+25
View File
@@ -4,3 +4,28 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
/**
* Return an Unsplash Source URL for a given query and size.
* Uses the Unsplash Source endpoint so no API key is required.
*/
export function getUnsplashImage(query: string, width = 1200, height = 675, seed?: string) {
const q = encodeURIComponent(query || 'nature');
// `sig` helps vary the image per post so images don't all cache to the same photo
const sig = seed ? `&sig=${encodeURIComponent(seed)}` : '';
return `https://source.unsplash.com/${width}x${height}/?${q}${sig}`;
}
// Load local blog images (as URLs) from the assets folder.
const _blogImageModules = import.meta.glob('../assets/blog-images/*', { eager: true, as: 'url' }) as Record<string, string>;
const _blogImageUrls = Object.values(_blogImageModules);
/**
* Pick a local blog image by numeric index. Images are assigned in order.
*/
export function getBlogImage(index?: number, fallback?: string) {
if (_blogImageUrls.length === 0) return fallback ?? '';
if (typeof index !== 'number' || isNaN(index)) return _blogImageUrls[0];
const idx = Math.abs(Math.floor(index)) % _blogImageUrls.length;
return _blogImageUrls[idx];
}
+182
View File
@@ -0,0 +1,182 @@
import { useEffect, useState } from 'react';
import { Link, useSearchParams } from 'react-router-dom';
import Header from '../components/landing/Header';
import Footer from '../components/landing/Footer';
import { Calendar, Clock, ArrowRight, Tag } from 'lucide-react';
import { getAllPosts, type BlogPost } from '../lib/blogs';
import { getBlogImage } from '../lib/utils';
import './blog-list.css';
const categoryColors: Record<string, string> = {
Strategy: 'bg-yellow-500/20 text-yellow-400',
Insights: 'bg-purple-500/20 text-purple-400',
Trends: 'bg-blue-500/20 text-blue-400',
Growth: 'bg-green-500/20 text-green-400',
Tutorial: 'bg-orange-500/20 text-orange-400',
};
const BlogList = () => {
const [searchParams] = useSearchParams();
const [posts, setPosts] = useState<BlogPost[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const previewValue = (searchParams.get('preview') || '').toLowerCase();
const isPreview = previewValue === '1' || previewValue === 'true' || previewValue === 'yes';
useEffect(() => {
let active = true;
setLoading(true);
setError(null);
try {
const all = getAllPosts();
if (!active) return;
// If frontmatter contains `live` flag, respect it; otherwise show all
const filtered = isPreview ? all : (all as any).filter ? (all as any).filter((p: any) => p.live !== false) : all;
setPosts(filtered as BlogPost[]);
} catch (err: any) {
setError(err?.message || 'Failed to load posts.');
} finally {
if (active) setLoading(false);
}
return () => {
active = false;
};
}, [isPreview]);
const [selectedCategory, setSelectedCategory] = useState<string>('All');
const categories = ['All', ...Array.from(new Set(posts.map((p) => (p as any).category || 'General'))).filter(Boolean)];
const displayed = selectedCategory === 'All' ? posts : posts.filter((p) => ((p as any).category || 'General') === selectedCategory);
const featuredDisplayed = displayed.length ? displayed[0] : undefined;
const latest = displayed.length > 1 ? displayed.slice(1) : [];
return (
<div className="min-h-screen bg-background">
<Header />
<main className="pt-32 pb-20">
<section className="container mx-auto px-6 mb-12">
{!loading && !featuredDisplayed && (
<div className="glass rounded-2xl border border-border/50 p-8 text-center text-muted-foreground">
{error || 'No articles available yet.'}
</div>
)}
{featuredDisplayed && (
<Link to={`/blog/${featuredDisplayed.slug}${isPreview ? '?preview=true' : ''}`} className="group block">
<div className="glass rounded-2xl overflow-hidden border border-border/50 hover:border-primary/30 transition-colors">
<div className="grid md:grid-cols-2 gap-0">
<div className="aspect-video md:aspect-auto overflow-hidden">
<img
src={getBlogImage(0)}
alt={featuredDisplayed.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
</div>
<div className="p-8 md:p-12 flex flex-col justify-center">
<div className="flex items-center gap-3 mb-4">
{/** category may be undefined in current setup */}
<span className={`px-3 py-1 rounded-full text-xs font-medium ${categoryColors[(featuredDisplayed as any).category] || 'bg-secondary text-secondary-foreground'}`}>
{(featuredDisplayed as any).category || 'General'}
</span>
<span className="text-sm text-muted-foreground">Featured</span>
</div>
<h2 className="text-2xl md:text-3xl font-bold text-foreground mb-4 group-hover:text-primary transition-colors">
{featuredDisplayed.title}
</h2>
<p className="text-muted-foreground mb-6">{featuredDisplayed.description}</p>
<div className="flex items-center gap-4 text-sm text-muted-foreground mb-6">
<span className="flex items-center gap-1">
<Calendar className="w-4 h-4" />
{featuredDisplayed.date}
</span>
<span className="flex items-center gap-1">
<Clock className="w-4 h-4" />
{/* readTime not available; omit if missing */}
{(featuredDisplayed as any).readTime || ''}
</span>
</div>
<span className="inline-flex items-center gap-2 text-primary font-semibold group-hover:gap-3 transition-all">
Read Article
<ArrowRight className="w-4 h-4" />
</span>
</div>
</div>
</div>
</Link>
)}
</section>
<section className="container mx-auto px-6">
<div className="flex items-center justify-between mb-8">
<h2 className="text-2xl font-bold text-foreground">Latest Articles</h2>
<div className="flex items-center gap-2">
<Tag className="w-4 h-4 text-muted-foreground" />
<label className="sr-only">Filter by category</label>
<select
value={selectedCategory}
onChange={(e) => setSelectedCategory(e.target.value)}
className="ml-2 bg-bg-secondary border border-border-subtle text-sm text-muted-foreground rounded-md px-3 py-1"
>
{categories.map((c) => (
<option key={c} value={c} className="bg-bg-secondary text-text-primary">
{c}
</option>
))}
</select>
</div>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
{loading && <div className="col-span-full text-center text-muted-foreground">Loading articles...</div>}
{!loading && latest.length === 0 && !!featuredDisplayed && (
<div className="col-span-full text-center text-muted-foreground">No additional articles yet.</div>
)}
{latest.map((p, i) => (
<Link key={p.slug} to={`/blog/${p.slug}${isPreview ? '?preview=true' : ''}`} className="group block">
<article className="glass rounded-xl overflow-hidden border border-border/50 hover:border-primary/30 transition-colors">
<div className="aspect-video overflow-hidden">
<img
src={getBlogImage(i + 1)}
alt={p.title}
className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500"
/>
</div>
<div className="p-6">
<div className="flex items-center gap-3 mb-3">
<span className={`px-3 py-1 rounded-full text-xs font-medium ${categoryColors[(p as any).category] || 'bg-secondary text-secondary-foreground'}`}>
{(p as any).category || 'General'}
</span>
</div>
<h3 className="text-lg font-bold text-foreground mb-3 group-hover:text-primary transition-colors line-clamp-2">{p.title}</h3>
<p className="text-sm text-muted-foreground mb-4 line-clamp-2">{p.description}</p>
<div className="flex items-center gap-4 text-xs text-muted-foreground">
<span className="flex items-center gap-1">
<Calendar className="w-3 h-3" />
{p.date}
</span>
<span className="flex items-center gap-1">
<Clock className="w-3 h-3" />
{(p as any).readTime || ''}
</span>
</div>
</div>
</article>
</Link>
))}
</div>
</section>
</main>
<Footer />
</div>
);
};
export default BlogList;
+83
View File
@@ -0,0 +1,83 @@
import { useEffect, useState } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import Header from '../components/landing/Header';
import Footer from '../components/landing/Footer';
import { getPostBySlug, getAllPosts, type BlogPost } from '../lib/blogs';
import { marked } from 'marked';
import { getBlogImage } from '../lib/utils';
import { Calendar, Clock, Tag } from 'lucide-react';
import './blog.css';
const BlogPost = () => {
const { slug } = useParams();
const [searchParams] = useSearchParams();
const [post, setPost] = useState<BlogPost | undefined>();
const [index, setIndex] = useState<number | undefined>(undefined);
useEffect(() => {
if (!slug) return;
const p = getPostBySlug(slug);
setPost(p);
if (p) {
const posts = getAllPosts();
const idx = posts.findIndex((x) => x.slug === p.slug);
setIndex(idx >= 0 ? idx : undefined);
}
}, [slug]);
if (!post) {
return (
<div className="min-h-screen bg-background">
<Header />
<main className="pt-24 p-6">
<div className="container mx-auto">
<div className="glass rounded-2xl p-8 text-center">Post not found.</div>
</div>
</main>
<Footer />
</div>
);
}
const isPreview = ['1', 'true', 'yes'].includes((searchParams.get('preview') || '').toLowerCase());
return (
<div className="min-h-screen bg-background">
<Header />
<main className="pt-32 pb-20">
<section className="container mx-auto px-6">
<div className="glass rounded-2xl overflow-hidden border border-border/50">
<div className="w-full">
{index !== undefined && (
<img
src={getBlogImage(index)}
alt={post.title}
className="w-full h-64 md:h-96 object-cover group-hover:scale-105 transition-transform duration-500"
/>
)}
<div className="p-8 md:p-12">
<div className="flex items-center gap-3 mb-3">
<span className="px-3 py-1 rounded-full text-xs font-medium bg-secondary text-secondary-foreground">{(post as any).category || 'General'}</span>
<div className="text-sm text-muted-foreground">{post.date} {isPreview ? '(Preview)' : ''}</div>
</div>
<h1 className="text-3xl md:text-4xl font-bold text-foreground mb-4">{post.title}</h1>
<div className="flex items-center gap-6 text-sm text-muted-foreground mb-6">
<span className="flex items-center gap-2"><Calendar className="w-4 h-4" />{post.date}</span>
<span className="flex items-center gap-2"><Clock className="w-4 h-4" />{(post as any).readTime || ''}</span>
</div>
<article className="prose max-w-none text-foreground post-content" dangerouslySetInnerHTML={{ __html: marked.parse(post.content) }} />
</div>
</div>
</div>
</section>
</main>
<Footer />
</div>
);
};
export default BlogPost;
+35
View File
@@ -0,0 +1,35 @@
.featured-card {
position: relative;
display: block;
}
.featured-card .featured-meta {
position: absolute;
left: 0;
bottom: 0;
right: 0;
background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,0.45) 40%, rgba(0,0,0,0.7) 100%);
color: white;
}
/* Slightly lift cards on hover */
.bento-card:hover {
transform: translateY(-6px);
box-shadow: 0 12px 40px rgba(0,0,0,0.45);
}
/* Make featured card text pop on dark backgrounds */
.featured-card h2,
.featured-card p {
color: var(--color-text-primary);
}
.featured-card img {
display: block;
}
@media (min-width: 768px) {
.featured-card .featured-meta {
padding: 2rem 3rem;
}
}
+109
View File
@@ -0,0 +1,109 @@
/* Simple blog content styles to improve readability for markdown-rendered posts */
.post-content {
color: var(--color-text-primary);
line-height: 1.8;
font-size: 1rem;
/* ensure nice spacing for paragraphs and headings */
word-break: break-word;
}
.post-content img {
display: block;
max-width: 100%;
border-radius: 0.5rem;
margin: 1rem 0;
box-shadow: 0 8px 24px rgba(0,0,0,0.45);
}
.post-content h2 {
font-size: 1.6rem;
margin-top: 1.6rem;
margin-bottom: 0.5rem;
color: var(--color-accent-primary);
line-height: 1.15;
}
.post-content h3 {
margin-top: 1.1rem;
margin-bottom: 0.45rem;
}
.post-content h1 {
font-size: 2rem;
color: var(--color-accent-primary);
margin-bottom: 0.6rem;
}
.post-content p {
margin: 0.6rem 0 0.9rem 0;
color: var(--color-text-muted);
font-size: 1.02rem;
}
.post-content blockquote {
border-left: 4px solid rgba(255,255,255,0.12);
padding: 0.75rem 1rem;
margin: 1rem 0;
color: var(--color-text-secondary);
background: rgba(255,255,255,0.02);
border-radius: 6px;
}
.post-content pre {
background: linear-gradient(180deg, rgba(0,0,0,0.55), rgba(0,0,0,0.45));
color: #e6ffed;
padding: 1rem;
border-radius: 8px;
overflow: auto;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, 'Roboto Mono', 'Courier New', monospace;
font-size: 0.92rem;
}
.post-content code {
background: rgba(0,0,0,0.28);
color: #e6ffed;
padding: 0.18rem 0.4rem;
border-radius: 6px;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, 'Roboto Mono', 'Courier New', monospace;
}
.post-content ul,
.post-content ol {
margin-left: 1.25rem;
margin-bottom: 0.75rem;
padding-left: 1rem;
list-style-position: outside;
}
.post-content ul {
list-style-type: disc;
}
.post-content ol {
list-style-type: decimal;
}
.post-content li {
margin-bottom: 0.45rem;
}
.post-content li::marker {
color: var(--color-text-muted);
}
.post-content a {
color: var(--color-accent-secondary);
text-decoration: none;
border-bottom: 1px dashed rgba(212,255,94,0.18);
}
.post-content a:hover {
color: var(--color-accent-primary);
border-bottom-color: rgba(255,254,119,0.35);
}
.post-meta {
color: var(--color-text-secondary);
font-size: 0.95rem;
margin-bottom: 0.75rem;
}
+4
View File
@@ -1 +1,5 @@
/// <reference types="vite/client" />
declare module '*.md' {
const content: string;
export default content;
}